]> granicus.if.org Git - postgis/commitdiff
#3589, Orientation checking and forcing fuctions
authorDaniel Baston <dbaston@gmail.com>
Fri, 17 Mar 2017 00:28:07 +0000 (00:28 +0000)
committerDaniel Baston <dbaston@gmail.com>
Fri, 17 Mar 2017 00:28:07 +0000 (00:28 +0000)
git-svn-id: http://svn.osgeo.org/postgis/trunk@15336 b70326c6-7e19-0410-871a-916f4a2858ee

13 files changed:
NEWS
doc/reference_accessor.xml
doc/reference_editor.xml
liblwgeom/cunit/cu_libgeom.c
liblwgeom/liblwgeom.h.in
liblwgeom/lwgeom.c
liblwgeom/lwpoly.c
liblwgeom/lwtriangle.c
postgis/lwgeom_functions_analytic.c
postgis/postgis.sql.in
regress/Makefile.in
regress/orientation.sql [new file with mode: 0644]
regress/orientation_expected [new file with mode: 0644]

diff --git a/NEWS b/NEWS
index 56ef39370cebf0fc98531e3dbd81c9fee1663f1d..de2fb277c683e6b23def2662719b0bfe16937d9f 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -5,6 +5,7 @@ PostGIS 2.4.0
 
   - #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
index 4a7b8a026d7b37b41ee132e613f58238ac9fc5c0..66ed9dcdb3dcd0a0a520329f353cdb13d5838e97 100644 (file)
@@ -872,6 +872,129 @@ FROM (SELECT ST_BuildArea(
          </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>
index 741d252662c2c5a7d4135c24c4f654c4ff48f9e8..d097d70878d1e8b7402688f96117d2d5399cdaa3 100644 (file)
@@ -449,6 +449,54 @@ SELECT  ST_AsEWKT(ST_Force4D('MULTILINESTRINGM((0 0 1,0 5 2,5 0 3,0 0 4),(1 1 1,
          </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,6 +579,53 @@ GEOMETRYCOLLECTION(
          </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>
@@ -585,11 +680,17 @@ GEOMETRYCOLLECTION(
                <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>
@@ -613,7 +714,12 @@ GEOMETRYCOLLECTION(
                <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>
index 7ae07f2a71e5fa5b075824c85e02f624f3bb6b5c..b325e2f8c8f26aeb8d5e26bda64720f463e875bd 100644 (file)
@@ -741,6 +741,7 @@ static void test_lwgeom_force_clockwise(void)
 
        /* 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 @@ static void test_lwgeom_force_clockwise(void)
 
        /* 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 @@ static void test_lwgeom_force_clockwise(void)
 
        /* 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 @@ static void test_lwgeom_force_clockwise(void)
 
        /* 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);
index d15afa8b3cb4d073f7977f1382486f206f7683da..07fa591be9ae924f7075043c1da2984a58fbbe16 100644 (file)
@@ -1218,6 +1218,9 @@ extern void lwgeom_force_clockwise(LWGEOM *lwgeom);
 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);
 
index 769d00193b63c5aa27938b5e9e0e3fea8ae01e2a..4d45ce0b5eecc1b5f804151e229b082b57fe7a2a 100644 (file)
@@ -59,6 +59,35 @@ lwgeom_force_clockwise(LWGEOM *lwgeom)
        }
 }
 
+/** 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)
index 4d2b8a450c22550dc436dbb6d8bf552de0dab7da..f8fa89cd78db21d7d8e64f26a0a502807850b052 100644 (file)
@@ -284,6 +284,24 @@ lwpoly_force_clockwise(LWPOLY *poly)
 
 }
 
+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)
 {
index b2896a8394c22a582fbc68f1bd7e9fbe3126ee76..d87b1e3b2d4398a553d3121c34bd2dbf2059daf7 100644 (file)
@@ -109,6 +109,12 @@ lwtriangle_force_clockwise(LWTRIANGLE *triangle)
                ptarray_reverse(triangle->points);
 }
 
+int
+lwtriangle_is_clockwise(LWTRIANGLE *triangle)
+{
+       return !ptarray_isccw(triangle->points);
+}
+
 void
 lwtriangle_reverse(LWTRIANGLE *triangle)
 {
index 922f5d98b103bd4808d4b538ed772c608330bf0f..7d4ad951a6d49b08fe0d767c09263d8010cc7af1 100644 (file)
@@ -55,6 +55,8 @@ Datum ST_LineCrossingDirection(PG_FUNCTION_ARGS);
 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 @@ Datum ST_GeometricMedian(PG_FUNCTION_ARGS)
        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);
+}
index 3ea1a82bc6b39e914719d79c75dcd4c11fa0f10d..4f58b0b8c28bf723ef674d60acbdff0f6c87da9a 100644 (file)
@@ -1235,6 +1235,20 @@ CREATE OR REPLACE FUNCTION ST_Area(geometry)
        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 @@ CREATE OR REPLACE FUNCTION ST_Reverse(geometry)
        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@.ST_Reverse(@extschema@.ST_ForcePolygonCW($1)) $$
+       LANGUAGE SQL IMMUTABLE STRICT _PARALLEL
+       COST 15;
+
 -- Availability: 1.2.2
 CREATE OR REPLACE FUNCTION ST_ForceRHR(geometry)
        RETURNS geometry
index c68d3f032c5b4712f16c74f0b80fa6031718c3eb..3301d4e2df98fee1cd6bd5dab32e9cf021392822 100644 (file)
@@ -102,6 +102,7 @@ TESTS = \
        minimum_bounding_circle \
        normalize \
        operators \
+       orientation \
        out_geometry \
        out_geography \
        polygonize \
diff --git a/regress/orientation.sql b/regress/orientation.sql
new file mode 100644 (file)
index 0000000..92e7b6f
--- /dev/null
@@ -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)))');
+
+
diff --git a/regress/orientation_expected b/regress/orientation_expected
new file mode 100644 (file)
index 0000000..cb8789f
--- /dev/null
@@ -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