Implement lwgeom_wrapx and ST_WrapX
authorSandro Santilli <strk@keybit.net>
Thu, 16 Jun 2016 09:09:26 +0000 (09:09 +0000)
committerSandro Santilli <strk@keybit.net>
Thu, 16 Jun 2016 09:09:26 +0000 (09:09 +0000)
Includes tests (both cunit and regress) and documentation.

Closes #454

git-svn-id: http://svn.osgeo.org/postgis/trunk@14961 b70326c6-7e19-0410-871a-916f4a2858ee

12 files changed:
NEWS
doc/reference_processing.xml
liblwgeom/Makefile.in
liblwgeom/cunit/Makefile.in
liblwgeom/cunit/cu_tester.c
liblwgeom/cunit/cu_wrapx.c [new file with mode: 0644]
liblwgeom/liblwgeom.h.in
liblwgeom/lwgeom_wrapx.c [new file with mode: 0644]
postgis/lwgeom_functions_basic.c
postgis/postgis.sql.in
regress/wrapx.sql [new file with mode: 0644]
regress/wrapx_expected [new file with mode: 0644]

diff --git a/NEWS b/NEWS
index 6dd21f4286c949fcc5fd2546572389b78fb2114c..4d9868407d7eeab4035a4f2652d8fc59d7ea7ece 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -17,6 +17,7 @@ PostGIS 2.3.0
   - Add parameters for geography ST_Buffer (Thomas Bonfort)
   - TopoGeom_addElement, TopoGeom_remElement (Sandro Santilli)
   - populate_topology_layer (Sandro Santilli)
+  - #454,  ST_WrapX and lwgeom_wrapx (Sandro Santilli)
   - #1758, ST_Normalize (Sandro Santilli)
   - #2259, ST_Voronoi (Dan Baston)
   - #2991, Enable ST_Transform to use PROJ.4 text (Mike Toews)
index bdce63eb8644ae9eb5f6e0df088db1ae58095c64..5b6da95e01f11801e7310ec58d0075a3a8e7db31 100644 (file)
@@ -2703,7 +2703,73 @@ LINESTRING(241.42 38.38,241.8 38.45)
          <!-- Optionally add a "See Also" section -->
          <refsection>
                <title>See Also</title>
-               <para><xref linkend="ST_GeomFromEWKT" />, <xref linkend="ST_GeomFromText" />, <xref linkend="ST_AsEWKT" /></para>
+               <para>
+      <xref linkend="ST_WrapX" />
+    </para>
+         </refsection>
+       </refentry>
+
+       <refentry id="ST_WrapX">
+         <refnamediv>
+               <refname>ST_WrapX</refname>
+
+               <refpurpose>Wrap a geometry around an X value.</refpurpose>
+         </refnamediv>
+
+         <refsynopsisdiv>
+               <funcsynopsis>
+                 <funcprototype>
+                       <funcdef>geometry <function>ST_WrapX</function></funcdef>
+                       <paramdef><type>geometry </type> <parameter>geom</parameter></paramdef>
+                       <paramdef><type>float8 </type> <parameter>wrap</parameter></paramdef>
+                       <paramdef><type>float8 </type> <parameter>move</parameter></paramdef>
+                 </funcprototype>
+               </funcsynopsis>
+         </refsynopsisdiv>
+
+         <refsection>
+               <title>Description</title>
+
+    <para>
+This function splits the input geometries and then moves every resulting
+component falling on the right (for negative 'move') or on the left (for
+positive 'move') of given 'wrap' line in the direction specified by the
+'move' parameter, finally re-unioning the pieces togheter.
+    </para>
+
+               <note><para>
+This is useful to "recenter" long-lat input to have features
+of interest not spawned from one side to the other.
+    </para></note>
+
+               <para>Availability: 2.3.0</para>
+
+               <para>&Z_support;</para>
+<!-- TODO: check these
+               <para>&P_support;</para>
+               <para>&T_support;</para>
+-->
+         </refsection>
+
+
+         <refsection>
+               <title>Examples</title>
+
+               <programlisting>
+-- Move all components of the given geometries whose bounding box
+-- falls completely on the left of x=0 to +360
+select ST_WrapX(the_geom, 0, 360);
+
+-- Move all components of the given geometries whose bounding box
+-- falls completely on the left of x=-30 to +360
+select ST_WrapX(the_geom, -30, 360);
+               </programlisting>
+         </refsection>
+
+         <!-- Optionally add a "See Also" section -->
+         <refsection>
+               <title>See Also</title>
+               <para><xref linkend="ST_Shift_Longitude" /></para>
          </refsection>
        </refentry>
 
index 1c76b53dcfafc578cc64d9dcf4500f4cf3b72650..f2652278f26274639cf65686c396ef4a6a4dd8b7 100644 (file)
@@ -112,6 +112,7 @@ SA_OBJS = \
        lwgeom_geos_split.o \
        lwgeom_topo.o \
        lwgeom_transform.o \
+       lwgeom_wrapx.o \
        lwunionfind.o \
        effectivearea.o \
        lwkmeans.o \
index f0e899d82afa52ad9d860c30345eec79684c4d30..e6cb4abe3530a4e6d46b0404de03c0a5652ab6c3 100644 (file)
@@ -62,6 +62,7 @@ OBJS= \
        cu_iterator.o \
        cu_varint.o \
        cu_unionfind.o \
+       cu_wrapx.o \
        cu_tester.o
 
 ifeq (@SFCGAL@,sfcgal)
index 7f266d5a70ee06839aa96a7962c5e139dcc9bb1c..3061598855727fd216c02dac6b5f1a8788cea443 100644 (file)
@@ -68,6 +68,7 @@ extern void wkb_out_suite_setup(void);
 extern void surface_suite_setup(void);
 extern void wkb_in_suite_setup(void);
 extern void wkt_in_suite_setup(void);
+extern void wrapx_suite_setup(void);
 
 
 /* AND ADD YOUR SUITE SETUP FUNCTION HERE (2 of 2) */
@@ -117,6 +118,7 @@ PG_SuiteSetup setupfuncs[] =
        wkb_out_suite_setup,
        wkt_in_suite_setup,
        wkt_out_suite_setup,
+       wrapx_suite_setup,
        NULL
 };
 
@@ -274,7 +276,7 @@ cu_noticereporter(const char *fmt, va_list ap)
   char buf[MAX_CUNIT_MSG_LENGTH+1];
   vsnprintf (buf, MAX_CUNIT_MSG_LENGTH, fmt, ap);
   buf[MAX_CUNIT_MSG_LENGTH]='\0';
-  /*fprintf(stderr, "NOTICE: %s\n", buf);*/
+  fprintf(stderr, "NOTICE: %s\n", buf);
 }
 
 static void
diff --git a/liblwgeom/cunit/cu_wrapx.c b/liblwgeom/cunit/cu_wrapx.c
new file mode 100644 (file)
index 0000000..bc5044b
--- /dev/null
@@ -0,0 +1,134 @@
+/**********************************************************************
+ *
+ * PostGIS - Spatial Types for PostgreSQL
+ * http://postgis.net
+ *
+ * Copyright (C) 2016 Sandro Santilli <strk@kbt.io>
+ *
+ * This is free software; you can redistribute and/or modify it under
+ * the terms of the GNU General Public Licence. See the COPYING file.
+ *
+ **********************************************************************/
+
+#include "CUnit/Basic.h"
+#include "cu_tester.h"
+
+#include "liblwgeom.h"
+#include "liblwgeom_internal.h"
+
+static void test_lwgeom_wrapx(void)
+{
+       LWGEOM *geom, *ret;
+       char *exp_wkt, *obt_wkt;
+
+       geom = lwgeom_from_wkt(
+               "POLYGON EMPTY",
+               LW_PARSER_CHECK_NONE);
+       CU_ASSERT(geom != NULL);
+       ret = lwgeom_wrapx(geom, 0, 20);
+       CU_ASSERT(ret != NULL);
+       obt_wkt = lwgeom_to_ewkt(ret);
+       exp_wkt = "POLYGON EMPTY";
+       ASSERT_STRING_EQUAL(obt_wkt, exp_wkt);
+       lwfree(obt_wkt);
+       lwgeom_free(ret);
+       lwgeom_free(geom);
+
+       geom = lwgeom_from_wkt(
+               "POINT(0 0)",
+               LW_PARSER_CHECK_NONE);
+       CU_ASSERT(geom != NULL);
+       ret = lwgeom_wrapx(geom, 2, 10);
+       CU_ASSERT(ret != NULL);
+       obt_wkt = lwgeom_to_ewkt(ret);
+       exp_wkt = "POINT(10 0)";
+       ASSERT_STRING_EQUAL(obt_wkt, exp_wkt);
+       lwfree(obt_wkt);
+       lwgeom_free(ret);
+       lwgeom_free(geom);
+
+       geom = lwgeom_from_wkt(
+               "POINT(0 0)",
+               LW_PARSER_CHECK_NONE);
+       CU_ASSERT(geom != NULL);
+       ret = lwgeom_wrapx(geom, 0, 20);
+       CU_ASSERT(ret != NULL);
+       obt_wkt = lwgeom_to_ewkt(ret);
+       exp_wkt = "POINT(0 0)";
+       ASSERT_STRING_EQUAL(obt_wkt, exp_wkt);
+       lwfree(obt_wkt);
+       lwgeom_free(ret);
+       lwgeom_free(geom);
+
+       geom = lwgeom_from_wkt(
+               "POINT(0 0)",
+               LW_PARSER_CHECK_NONE);
+       CU_ASSERT(geom != NULL);
+       ret = lwgeom_wrapx(geom, 0, -20);
+       CU_ASSERT(ret != NULL);
+       obt_wkt = lwgeom_to_ewkt(ret);
+       exp_wkt = "POINT(0 0)";
+       ASSERT_STRING_EQUAL(obt_wkt, exp_wkt);
+       lwfree(obt_wkt);
+       lwgeom_free(ret);
+       lwgeom_free(geom);
+
+       geom = lwgeom_from_wkt(
+               "LINESTRING(0 0,10 0)",
+               LW_PARSER_CHECK_NONE);
+       CU_ASSERT(geom != NULL);
+       ret = lwgeom_wrapx(geom, 8, -10);
+       CU_ASSERT(ret != NULL);
+       obt_wkt = lwgeom_to_ewkt(ret);
+       exp_wkt = "MULTILINESTRING((0 0,8 0),(-2 0,0 0))";
+       ASSERT_STRING_EQUAL(obt_wkt, exp_wkt);
+       lwfree(obt_wkt);
+       lwgeom_free(ret);
+       lwgeom_free(geom);
+
+       geom = lwgeom_from_wkt(
+               "MULTILINESTRING((-5 -2,0 0),(0 0,10 10))",
+               LW_PARSER_CHECK_NONE);
+       CU_ASSERT(geom != NULL);
+       ret = lwgeom_wrapx(geom, 0, 20);
+       CU_ASSERT(ret != NULL);
+       obt_wkt = lwgeom_to_ewkt(ret);
+       exp_wkt = "MULTILINESTRING((15 -2,20 0),(0 0,10 10))";
+       ASSERT_STRING_EQUAL(obt_wkt, exp_wkt);
+       lwfree(obt_wkt);
+       lwgeom_free(ret);
+       lwgeom_free(geom);
+
+       geom = lwgeom_from_wkt(
+               "GEOMETRYCOLLECTION("
+               " MULTILINESTRING((-5 -2,0 0),(0 0,10 10)),"
+               " POINT(-5 0),"
+               " POLYGON EMPTY"
+               ")",
+               LW_PARSER_CHECK_NONE);
+       CU_ASSERT(geom != NULL);
+       ret = lwgeom_wrapx(geom, 0, 20);
+       CU_ASSERT(ret != NULL);
+       obt_wkt = lwgeom_to_ewkt(ret);
+       exp_wkt = "GEOMETRYCOLLECTION("
+                                               "MULTILINESTRING((15 -2,20 0),(0 0,10 10)),"
+                                               "POINT(15 0),"
+                                               "POLYGON EMPTY"
+                                               ")";
+       ASSERT_STRING_EQUAL(obt_wkt, exp_wkt);
+       lwfree(obt_wkt);
+       lwgeom_free(ret);
+       lwgeom_free(geom);
+
+}
+
+
+/*
+** Used by test harness to register the tests in this file.
+*/
+void wrapx_suite_setup(void);
+void wrapx_suite_setup(void)
+{
+       CU_pSuite suite = CU_add_suite("wrapx", NULL, NULL);
+       PG_ADD_TEST(suite, test_lwgeom_wrapx);
+}
index d36fa6081fa1f6b6fd505e5adf203d329d38899f..c028dc221d1b0e1cf915a7029784645321dacc25 100644 (file)
@@ -1218,6 +1218,20 @@ extern void interpolate_point4d(POINT4D *A, POINT4D *B, POINT4D *I, double F);
 
 void lwgeom_longitude_shift(LWGEOM *lwgeom);
 
+/**
+ * @brief wrap geometry on given cut x value
+ *
+ * For a positive amount, shifts anything that is on the left
+ * of "cutx" to the right by the given amount.
+ *
+ * For a negative amount, shifts anything that is on the right
+ * of "cutx" to the left by the given absolute amount.
+ *
+ * @param cutx the X value to perform wrapping on
+ * @param amount shift amount and wrapping direction
+ */
+LWGEOM *lwgeom_wrapx(const LWGEOM *lwgeom, double cutx, double amount);
+
 
 /**
 * @brief Check whether or not a lwgeom is big enough to warrant a bounding box.
diff --git a/liblwgeom/lwgeom_wrapx.c b/liblwgeom/lwgeom_wrapx.c
new file mode 100644 (file)
index 0000000..1f35d98
--- /dev/null
@@ -0,0 +1,190 @@
+/**********************************************************************
+ *
+ * PostGIS - Spatial Types for PostgreSQL
+ * http://postgis.net
+ *
+ * PostGIS is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * PostGIS is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with PostGIS.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ **********************************************************************
+ *
+ * Copyright 2016 Sandro Santilli <strk@kbt.net>
+ *
+ **********************************************************************/
+
+#include "../postgis_config.h"
+#define POSTGIS_DEBUG_LEVEL 4
+#include "lwgeom_geos.h"
+#include "liblwgeom_internal.h"
+
+#include <string.h>
+#include <assert.h>
+
+LWGEOM* lwgeom_wrapx(const LWGEOM* lwgeom_in, double cutx, double amount);
+static LWCOLLECTION* lwcollection_wrapx(const LWCOLLECTION* lwcoll_in, double cutx, double amount);
+
+static LWGEOM*
+lwgeom_split_wrapx(const LWGEOM* geom_in, double cutx, double amount)
+{
+       LWGEOM *blade, *split;
+       POINTARRAY *bladepa;
+       POINT4D pt;
+       const GBOX *box_in;
+       AFFINE affine = {
+               1, 0, 0,
+               0, 1, 0,
+               0, 0, 1,
+               amount, 0, 0,
+       };
+
+       /* Extract box */
+       /* TODO: check if the bbox should be force-recomputed */
+       box_in = lwgeom_get_bbox(geom_in);
+       if ( ! box_in ) {
+               /* must be empty */
+               return lwgeom_clone(geom_in);
+       }
+
+       LWDEBUGF(2, "BOX X range is %g..%g, cutx:%g, amount:%g", box_in->xmin, box_in->xmax, cutx, amount);
+
+       /* Check if geometry is fully on the side needing shift */
+       if ( ( amount < 0 && box_in->xmin >= cutx ) || ( amount > 0 && box_in->xmax <= cutx ) )
+       {
+               split = lwgeom_clone_deep(geom_in);
+               LWDEBUG(2, "returning the translated geometry");
+               lwgeom_affine(split, &affine);
+               return split;
+       }
+
+//DEBUG2: [lwgeom_wrapx.c:lwgeom_split_wrapx:58] BOX X range is 8..10, cutx:8, amount:-10
+
+       /* Check if geometry is fully on the side needing no shift */
+       if ( ( amount < 0 && box_in->xmax <= cutx ) || ( amount > 0 && box_in->xmin >= cutx ) )
+       {
+               LWDEBUG(2, "returning the cloned geometry");
+               return lwgeom_clone_deep(geom_in);
+       }
+
+       /* We need splitting here */
+
+       /* construct blade */
+       bladepa = ptarray_construct(0, 0, 2);
+       pt.x = cutx;
+       pt.y = box_in->ymin - 1;
+       ptarray_set_point4d(bladepa, 0, &pt);
+       pt.y = box_in->ymax + 1;
+       ptarray_set_point4d(bladepa, 1, &pt);
+       blade = lwline_as_lwgeom(lwline_construct(geom_in->srid, NULL, bladepa));
+
+       LWDEBUG(2, "splitting the geometry");
+
+       /* split by blade */
+       split = lwgeom_split(geom_in, blade);
+       lwgeom_free(blade);
+
+       /* iterate over components, translate if needed */
+       const LWCOLLECTION *col = lwgeom_as_lwcollection(split);
+       if ( ! col ) {
+               /* not split, this is unexpected */
+               lwnotice("WARNING: unexpected lack of split in lwgeom_split_wrapx");
+               return lwgeom_clone(geom_in);
+       }
+       LWCOLLECTION *col_out = lwcollection_wrapx(col, cutx, amount);
+       lwgeom_free(split);
+
+       /* unary-union the result (homogenize too ?) */
+       LWGEOM* out = lwgeom_unaryunion(lwcollection_as_lwgeom(col_out));
+       lwcollection_free(col_out);
+
+       return out;
+}
+
+static LWCOLLECTION*
+lwcollection_wrapx(const LWCOLLECTION* lwcoll_in, double cutx, double amount)
+{
+       LWGEOM** wrap_geoms=NULL;
+       LWCOLLECTION* out;
+       size_t i;
+
+       wrap_geoms = lwalloc(lwcoll_in->ngeoms * sizeof(LWGEOM*));
+       if ( ! wrap_geoms )
+       {
+               lwerror("Out of virtual memory");
+               return NULL;
+       }
+
+       for (i=0; i<lwcoll_in->ngeoms; ++i)
+       {
+               wrap_geoms[i] = lwgeom_wrapx(lwcoll_in->geoms[i], cutx, amount);
+               /* an exception should prevent this from ever returning NULL */
+               if ( ! wrap_geoms[i] ) {
+                       while (--i>=0) lwgeom_free(wrap_geoms[i]);
+                       lwfree(wrap_geoms);
+                       return NULL;
+               }
+       }
+
+       /* Now wrap_geoms has wrap_geoms_size geometries */
+       out = lwcollection_construct(lwcoll_in->type, lwcoll_in->srid,
+                                    NULL, lwcoll_in->ngeoms, wrap_geoms);
+
+       return out;
+}
+
+/* exported */
+LWGEOM*
+lwgeom_wrapx(const LWGEOM* lwgeom_in, double cutx, double amount)
+{
+       /* Nothing to wrap in an empty geom */
+       if ( lwgeom_is_empty(lwgeom_in) ) return lwgeom_clone(lwgeom_in);
+
+       /* Nothing to wrap if shift amount is zero */
+       if ( amount == 0 ) return lwgeom_clone(lwgeom_in);
+
+       switch (lwgeom_in->type)
+       {
+       case LINETYPE:
+       case POLYGONTYPE:
+               return lwgeom_split_wrapx(lwgeom_in, cutx, amount);
+
+       case POINTTYPE:
+       {
+               const LWPOINT *pt = lwgeom_as_lwpoint(lwgeom_clone_deep(lwgeom_in));
+               POINT4D pt4d;
+               getPoint4d_p(pt->point, 0, &pt4d);
+
+               LWDEBUGF(2, "POINT X is %g, cutx:%g, amount:%g", pt4d.x, cutx, amount);
+
+               if ( ( amount < 0 && pt4d.x > cutx ) || ( amount > 0 && pt4d.x < cutx ) )
+               {
+                       pt4d.x += amount;
+                       ptarray_set_point4d(pt->point, 0, &pt4d);
+               }
+               return lwpoint_as_lwgeom(pt);
+       }
+
+       case MULTIPOINTTYPE:
+       case MULTIPOLYGONTYPE:
+       case MULTILINETYPE:
+       case COLLECTIONTYPE:
+               return lwcollection_as_lwgeom(
+                                               lwcollection_wrapx((const LWCOLLECTION*)lwgeom_in, cutx, amount)
+                                        );
+
+       default:
+               lwerror("Wrapping of %s geometries is unsupported",
+                       lwtype_name(lwgeom_in->type));
+               return NULL;
+       }
+
+}
index f023f5aa857c7b710d18fb7a634bd8b6291e3896..34e85f3dbd1633f9ec38cefb33b2b588c769981f 100644 (file)
@@ -110,6 +110,7 @@ Datum ST_MakeEnvelope(PG_FUNCTION_ARGS);
 Datum ST_CollectionExtract(PG_FUNCTION_ARGS);
 Datum ST_CollectionHomogenize(PG_FUNCTION_ARGS);
 Datum ST_IsCollection(PG_FUNCTION_ARGS);
+Datum ST_WrapX(PG_FUNCTION_ARGS);
 
 
 /*------------------------------------------------------------------*/
@@ -1057,6 +1058,37 @@ Datum LWGEOM_longitude_shift(PG_FUNCTION_ARGS)
        PG_RETURN_POINTER(ret);
 }
 
+PG_FUNCTION_INFO_V1(ST_WrapX);
+Datum ST_WrapX(PG_FUNCTION_ARGS)
+{
+       Datum gdatum;
+       GSERIALIZED *geom_in;
+       LWGEOM *lwgeom_in, *lwgeom_out;
+       GSERIALIZED *geom_out;
+       double cutx;
+       double amount;
+
+       POSTGIS_DEBUG(2, "ST_WrapX called.");
+
+       gdatum = PG_GETARG_DATUM(0);
+       cutx = PG_GETARG_FLOAT8(1);
+       amount = PG_GETARG_FLOAT8(2);
+
+       //if ( ! amount ) PG_RETURN_DATUM(gdatum);
+
+       geom_in = ((GSERIALIZED *)PG_DETOAST_DATUM(gdatum));
+       lwgeom_in = lwgeom_from_gserialized(geom_in);
+
+       lwgeom_out = lwgeom_wrapx(lwgeom_in, cutx, amount);
+       geom_out = geometry_serialize(lwgeom_out);
+
+       lwgeom_free(lwgeom_in);
+       lwgeom_free(lwgeom_out);
+       PG_FREE_IF_COPY(geom_in, 0);
+
+       PG_RETURN_POINTER(geom_out);
+}
+
 PG_FUNCTION_INFO_V1(LWGEOM_inside_circle_point);
 Datum LWGEOM_inside_circle_point(PG_FUNCTION_ARGS)
 {
index d53be8df35f5804f57d9967cfb0ed77f9a5454fd..59b3d177cd9bede011b69aff3838ed7f50dd9396 100644 (file)
@@ -887,6 +887,12 @@ CREATE OR REPLACE FUNCTION ST_ShiftLongitude(geometry)
        AS 'MODULE_PATHNAME', 'LWGEOM_longitude_shift'
        LANGUAGE 'c' IMMUTABLE STRICT _PARALLEL;
 
+-- Availability: 2.3.0
+CREATE OR REPLACE FUNCTION ST_WrapX(geom geometry, wrap float8, move float8)
+       RETURNS geometry
+       AS 'MODULE_PATHNAME', 'ST_WrapX'
+       LANGUAGE 'c' IMMUTABLE STRICT _PARALLEL;
+
 -- Availability: 1.2.2
 -- Deprecation in 2.2.0
 CREATE OR REPLACE FUNCTION ST_Shift_Longitude(geometry)
diff --git a/regress/wrapx.sql b/regress/wrapx.sql
new file mode 100644 (file)
index 0000000..d08dc9c
--- /dev/null
@@ -0,0 +1,94 @@
+CREATE FUNCTION test(geom geometry, wrap float8, amount float8, exp geometry)
+RETURNS text AS $$
+DECLARE
+  obt geometry;
+BEGIN
+  obt = ST_Normalize(ST_WrapX(geom, wrap, amount));
+  IF ST_OrderingEquals(obt, exp) THEN
+    RETURN 'OK';
+  ELSE
+    RETURN 'KO:' || ST_AsEWKT(obt) || ' != ' || ST_AsEWKT(exp);
+  END IF;
+END
+$$ LANGUAGE plpgsql;
+
+SELECT 'P1', test(
+  'POINT(0 0)', 2, 10,
+  'POINT(10 0)');
+
+SELECT 'P2', test(
+  'POINT(0 0)', 2, -10,
+  'POINT(0 0)');
+
+SELECT 'P3', test(
+  'POINT(0 0)', -2, -10,
+  'POINT(-10 0)');
+
+SELECT 'L1', test(
+  'LINESTRING(0 0,10 0)', 2, 10,
+  --'LINESTRING(2 0,12 0)');
+  'MULTILINESTRING((10 0,12 0),(2 0,10 0))');
+
+SELECT 'L2', test(
+  'LINESTRING(0 0,10 0)', 8, -10,
+  'MULTILINESTRING((0 0,8 0),(-2 0,0 0))');
+
+SELECT 'L3', test(
+  'LINESTRING(0 0,10 0)', 0, 10,
+  'LINESTRING(0 0,10 0)');
+
+SELECT 'L4', test(
+  'LINESTRING(0 0,10 0)', 10, -10,
+  'LINESTRING(0 0,10 0)');
+
+SELECT 'ML1', test(
+  'MULTILINESTRING((-10 0,0 0),(0 0,10 0))', 0, 20,
+  'MULTILINESTRING((10 0,20 0),(0 0,10 0))');
+
+SELECT 'ML2', test(
+  'MULTILINESTRING((-10 0,0 0),(0 0,10 0))', 0, -20,
+  'MULTILINESTRING((-10 0,0 0),(-20 0,-10 0))');
+
+SELECT 'ML3', test(
+  'MULTILINESTRING((10 0,5 0),(-10 0,0 0),(0 0,5 0))', 0, -20,
+  'MULTILINESTRING((-10 0,0 0),(-15 0,-10 0),(-20 0,-15 0))');
+
+SELECT 'A1', test(
+  'POLYGON((0 0,10 0,10 10,0 10,0 0),
+           (1 2,3 2,3 4,1 4,1 2),
+           (4 2,6 2,6 4,4 4,4 2),
+           (7 2,9 2,9 4,7 4,7 2))', 5, 10,
+  'POLYGON((5 0,5 2,6 2,6 4,5 4,5 10,10 10,15 10,15 4,14 4,14 2,15 2,15 0,10 0,5 0),
+           (11 2,13 2,13 4,11 4,11 2),
+           (7 2,9 2,9 4,7 4,7 2))');
+
+SELECT 'A2', test(
+  'POLYGON((0 0,10 0,10 10,0 10,0 0),
+           (1 2,3 2,3 4,1 4,1 2),
+           (4 2,6 2,6 4,4 4,4 2),
+           (7 2,9 2,9 4,7 4,7 2))', 5, -10,
+  'POLYGON((-5 0,-5 2,-4 2,-4 4,-5 4,-5 10,0 10,5 10,5 4,4 4,4 2,5 2,5 0,0 0,-5 0),
+           (1 2,3 2,3 4,1 4,1 2),
+           (-3 2,-1 2,-1 4,-3 4,-3 2))');
+
+SELECT 'C1', test(
+  'GEOMETRYCOLLECTION(
+    POLYGON((0 0,10 0,10 10,0 10,0 0),
+            (1 2,3 2,3 4,1 4,1 2),
+            (4 2,6 2,6 4,4 4,4 2),
+            (7 2,9 2,9 4,7 4,7 2)),
+    POINT(2 20),
+    POINT(7 -20),
+    LINESTRING(0 40,10 40)
+  )',
+  5, -10,
+  'GEOMETRYCOLLECTION(
+    POLYGON((-5 0,-5 2,-4 2,-4 4,-5 4,-5 10,0 10,5 10,5 4,4 4,4 2,5 2,5 0,0 0,-5 0),
+            (1 2,3 2,3 4,1 4,1 2),
+            (-3 2,-1 2,-1 4,-3 4,-3 2)),
+    MULTILINESTRING((0 40,5 40),(-5 40,0 40)),
+    POINT(2 20),
+    POINT(-3 -20)
+  )');
+
+DROP FUNCTION test(geometry, float8, float8, geometry);
diff --git a/regress/wrapx_expected b/regress/wrapx_expected
new file mode 100644 (file)
index 0000000..79be8f3
--- /dev/null
@@ -0,0 +1,13 @@
+P1|OK
+P2|OK
+P3|OK
+L1|OK
+L2|OK
+L3|OK
+L4|OK
+ML1|OK
+ML2|OK
+ML3|OK
+A1|OK
+A2|OK
+C1|OK