]> granicus.if.org Git - postgis/commitdiff
Add ST_SwapOrdinates function
authorSandro Santilli <strk@keybit.net>
Wed, 25 Feb 2015 15:03:31 +0000 (15:03 +0000)
committerSandro Santilli <strk@keybit.net>
Wed, 25 Feb 2015 15:03:31 +0000 (15:03 +0000)
This is a generalization of ST_FlipCoordinates

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

NEWS
doc/reference_processing.xml
liblwgeom/liblwgeom.h.in
liblwgeom/liblwgeom_internal.h
liblwgeom/lwgeom.c
liblwgeom/ptarray.c
postgis/lwgeom_functions_basic.c
postgis/postgis.sql.in
regress/Makefile.in
regress/swapordinates.sql [new file with mode: 0644]
regress/swapordinates_expected [new file with mode: 0644]

diff --git a/NEWS b/NEWS
index b9b4ae18dffb31d3fd69cab263710a575ed60652..7faa372850ff3ea281830c73d0ef72534cf98b27 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -26,6 +26,7 @@ PostGIS 2.2.0
 
  * New Features *
 
+  - ST_SwapOrdinates (Sandro Santilli / Boundless)
   - #3040, KNN GiST index based centroid (<<->>) and box (<<#>>)
            n-D distance operators (Sandro Santilli / Boundless)
   - Interruptibility API for liblwgeom (Sandro Santilli / CartoDB)
index 357340ac612bc08ec15fb59f20d0fbc0655f90d0..a5a9e2697bb4977bb4a58a4313ae2dda21e992dc 100644 (file)
@@ -1606,6 +1606,73 @@ POINT(2 1)
                 ]]></programlisting>
          </refsection>
 
+         <!-- Optionally add a "See Also" section -->
+         <refsection>
+               <title>See Also</title>
+               <para> <xref linkend="ST_SwapOrdinates" /> </para>
+         </refsection>
+
+       </refentry>
+
+       <refentry id="ST_SwapOrdinates">
+         <refnamediv>
+               <refname>ST_SwapOrdinates</refname>
+               <refpurpose>Returns a version of the given geometry with
+                               given ordinate values swapped.
+    </refpurpose>
+         </refnamediv>
+
+         <refsynopsisdiv>
+               <funcsynopsis>
+                 <funcprototype>
+                       <funcdef>geometry <function>ST_SwapOrdinates</function></funcdef>
+                       <paramdef><type>geometry</type> <parameter>geom</parameter></paramdef>
+                       <paramdef><type>cstring</type> <parameter>ords</parameter></paramdef>
+                 </funcprototype>
+               </funcsynopsis>
+         </refsynopsisdiv>
+
+         <refsection>
+               <title>Description</title>
+               <para>
+Returns a version of the given geometry with given ordinates swapped.
+    </para>
+               <para>
+The <varname>ords</varname> parameter is a 2-characters string naming
+the ordinates to swap. Valid names are: x,y,z and m.
+    </para>
+               <para>&curve_support;</para>
+               <para>&Z_support;</para>
+               <para>&M_support;</para>
+               <para>Availability: 2.2.0</para>
+               <para>&P_support;</para>
+               <para>&T_support;</para>
+         </refsection>
+
+         <refsection>
+               <title>Example</title>
+               <programlisting><![CDATA[
+-- Scale M value by 2
+SELECT ST_AsText(
+  ST_SwapOrdinates(
+    ST_Scale(
+      ST_SwapOrdinates(g,'xm'),
+      2, 1
+    ),
+  'xm')
+) FROM ( SELECT 'POINT ZM (0 0 0 2)'::geometry g ) foo;
+     st_astext
+--------------------
+ POINT ZM (0 0 0 4)
+                ]]></programlisting>
+         </refsection>
+
+         <!-- Optionally add a "See Also" section -->
+         <refsection>
+               <title>See Also</title>
+               <para> <xref linkend="ST_FlipCoordinates" /> </para>
+         </refsection>
+
        </refentry>
 
        <refentry id="ST_Intersection">
index 0c0b807062e4e4d0a8971b552a10f569d4a9b4eb..4fc474b6b877303444b3e3584fe64075a154fc60 100644 (file)
 #define WKBSRIDFLAG 0x20000000
 #define WKBBBOXFLAG 0x10000000
 
+/** Ordinate names */
+typedef enum LWORD_T {
+  LWORD_X = 0,
+  LWORD_Y = 1,
+  LWORD_Z = 2,
+  LWORD_M = 3
+} LWORD;
+
 /**********************************************************************
 ** Spherical radius.
 ** Moritz, H. (1980). Geodetic Reference System 1980, by resolution of 
@@ -1547,9 +1555,26 @@ extern LWGEOM* lwgeom_remove_repeated_points(LWGEOM *in);
 
 extern char lwtriangle_is_repeated_points(LWTRIANGLE *triangle);
 
+/**
+ * Swap ordinate values in every vertex of the geometry.
+ *
+ * Ordinates to swap are specified using an index with meaning:
+ * 0=x, 1=y, 2=z, 3=m
+ *
+ * Swapping an existing ordinate with an unexisting one results
+ * in undefined value being written in the existing ordinate.
+ * Caller should verify and prevent such calls.
+ *
+ * Availability: 2.2.0
+ */
+extern void lwgeom_swap_ordinates(LWGEOM *in, LWORD o1, LWORD o2);
+
 /**
 * Reverse the X and Y coordinate order. Useful for geometries in lat/lon order
 * than need to be converted to lon/lat order.
+*
+* NOTE: uses lwgeom_swap_ordinates internally,
+*       kept for backward compatibility
 */
 extern LWGEOM* lwgeom_flip_coordinates(LWGEOM *in);
 
index a1eafdb7f22c2a7cdaa37f39b5693b8b6cc0e855..ab9fc446d12acd5547789d7865a23cf8f0e3adf1 100644 (file)
@@ -154,6 +154,13 @@ LWPOLY* lwpoly_force_dims(const LWPOLY *lwpoly, int hasz, int hasm);
 LWCOLLECTION* lwcollection_force_dims(const LWCOLLECTION *lwcol, int hasz, int hasm);
 POINTARRAY* ptarray_force_dims(const POINTARRAY *pa, int hasz, int hasm);
 
+/**
+ * Swap ordinate values o1 and o2 on a given POINTARRAY
+ *
+ * Ordinates semantic is: 0=x 1=y 2=z 3=m
+ */
+void ptarray_swap_ordinates(POINTARRAY *pa, LWORD o1, LWORD o2);
+
 /*
 * Is Empty?
 */
index 07faeaf23a84d4aee271d5f48c9dd6d8be858191..23efa18f6b3133c8a7d4814bb38ba76f785d5eec 100644 (file)
@@ -1427,13 +1427,25 @@ extern LWGEOM* lwgeom_remove_repeated_points(LWGEOM *in)
 }
 
 LWGEOM* lwgeom_flip_coordinates(LWGEOM *in)
+{
+  lwgeom_swap_ordinates(in,LWORD_X,LWORD_Y);
+  return in;
+}
+
+void lwgeom_swap_ordinates(LWGEOM *in, LWORD o1, LWORD o2)
 {
        LWCOLLECTION *col;
        LWPOLY *poly;
        int i;
 
-       if ( (!in) || lwgeom_is_empty(in) )
-               return in;
+#if PARANOIA_LEVEL > 0
+  assert(o1 < 4);
+  assert(o2 < 4);
+#endif
+
+       if ( (!in) || lwgeom_is_empty(in) ) return;
+
+  /* TODO: check for lwgeom NOT having the specified dimension ? */
 
        LWDEBUGF(4, "lwgeom_flip_coordinates, got type: %s",
                 lwtype_name(in->type));
@@ -1441,27 +1453,27 @@ LWGEOM* lwgeom_flip_coordinates(LWGEOM *in)
        switch (in->type)
        {
        case POINTTYPE:
-               ptarray_flip_coordinates(lwgeom_as_lwpoint(in)->point);
+               ptarray_swap_ordinates(lwgeom_as_lwpoint(in)->point, o1, o2);
                break;
 
        case LINETYPE:
-               ptarray_flip_coordinates(lwgeom_as_lwline(in)->points);
+               ptarray_swap_ordinates(lwgeom_as_lwline(in)->points, o1, o2);
                break;
 
        case CIRCSTRINGTYPE:
-               ptarray_flip_coordinates(lwgeom_as_lwcircstring(in)->points);
+               ptarray_swap_ordinates(lwgeom_as_lwcircstring(in)->points, o1, o2);
                break;
 
        case POLYGONTYPE:
                poly = (LWPOLY *) in;
                for (i=0; i<poly->nrings; i++)
                {
-                       ptarray_flip_coordinates(poly->rings[i]);
+                       ptarray_swap_ordinates(poly->rings[i], o1, o2);
                }
                break;
 
        case TRIANGLETYPE:
-               ptarray_flip_coordinates(lwgeom_as_lwtriangle(in)->points);
+               ptarray_swap_ordinates(lwgeom_as_lwtriangle(in)->points, o1, o2);
                break;
 
        case MULTIPOINTTYPE:
@@ -1477,19 +1489,21 @@ LWGEOM* lwgeom_flip_coordinates(LWGEOM *in)
                col = (LWCOLLECTION *) in;
                for (i=0; i<col->ngeoms; i++)
                {
-                       lwgeom_flip_coordinates(col->geoms[i]);
+                       lwgeom_swap_ordinates(col->geoms[i], o1, o2);
                }
                break;
 
        default:
-               lwerror("lwgeom_flip_coordinates: unsupported geometry type: %s",
+               lwerror("lwgeom_swap_ordinates: unsupported geometry type: %s",
                        lwtype_name(in->type));
-               return NULL;
+               return;
        }
 
-       lwgeom_drop_bbox(in);
-       lwgeom_add_bbox(in);
-       return in;
+  /* only refresh bbox if X or Y changed */
+  if ( o1 < 2 || o2 < 2 ) {
+    lwgeom_drop_bbox(in);
+    lwgeom_add_bbox(in);
+  }
 }
 
 void lwgeom_set_srid(LWGEOM *geom, int32_t srid)
index a734c68d87686368f3a4053920396d2aa12289a5..44a7f1ab55fcd69491fd7346ca9a360170bae54c 100644 (file)
@@ -369,6 +369,30 @@ ptarray_flip_coordinates(POINTARRAY *pa)
        return pa;
 }
 
+void
+ptarray_swap_ordinates(POINTARRAY *pa, LWORD o1, LWORD o2)
+{
+       int i;
+       double d, *dp1, *dp2;
+       POINT4D p;
+
+#if PARANOIA_LEVEL > 0
+  assert(o1 < 4);
+  assert(o2 < 4);
+#endif
+
+  dp1 = ((double*)&p)+(unsigned)o1;
+  dp2 = ((double*)&p)+(unsigned)o2;
+       for (i=0 ; i < pa->npoints ; i++)
+       {
+               getPoint4d_p(pa, i, &p);
+               d = *dp2;
+               *dp2 = *dp1;
+               *dp1 = d;
+               ptarray_set_point4d(pa, i, &p);
+       }
+}
+
 
 /**
  * @brief Returns a modified #POINTARRAY so that no segment is
index c78c6d61c02882b52720a03fe865bf2f70e426cc..b7038763a57195e28ac03483710cc002aa479bd0 100644 (file)
@@ -2714,16 +2714,71 @@ Datum ST_FlipCoordinates(PG_FUNCTION_ARGS);
 PG_FUNCTION_INFO_V1(ST_FlipCoordinates);
 Datum ST_FlipCoordinates(PG_FUNCTION_ARGS)
 {
-       GSERIALIZED *input = (GSERIALIZED *)PG_DETOAST_DATUM_COPY(PG_GETARG_DATUM(0));
-       GSERIALIZED *output;
-       LWGEOM *lwgeom_in = lwgeom_from_gserialized(input);
-       LWGEOM *lwgeom_out;
+       GSERIALIZED *in = (GSERIALIZED *)PG_DETOAST_DATUM_COPY(PG_GETARG_DATUM(0));
+       GSERIALIZED *out;
+       LWGEOM *lwgeom = lwgeom_from_gserialized(in);
 
-       lwgeom_out = lwgeom_flip_coordinates(lwgeom_in);
-       output = geometry_serialize(lwgeom_out);
+       lwgeom_swap_ordinates(lwgeom, LWORD_X, LWORD_Y);
+       out = geometry_serialize(lwgeom);
 
-       lwgeom_free(lwgeom_in);
-       PG_FREE_IF_COPY(input, 0);
+       lwgeom_free(lwgeom);
+       PG_FREE_IF_COPY(in, 0);
 
-       PG_RETURN_POINTER(output);
+       PG_RETURN_POINTER(out);
+}
+
+static LWORD ordname2ordval(char n)
+{
+  if ( n == 'x' || n == 'X' ) return LWORD_X;
+  if ( n == 'y' || n == 'y' ) return LWORD_Y;
+  if ( n == 'z' || n == 'Z' ) return LWORD_Z;
+  if ( n == 'm' || n == 'M' ) return LWORD_M;
+  lwerror("Invalid ordinate name '%c'. Expected x,y,z or m", n);
+  return (LWORD)-1;
+}
+
+Datum ST_SwapOrdinates(PG_FUNCTION_ARGS);
+PG_FUNCTION_INFO_V1(ST_SwapOrdinates);
+Datum ST_SwapOrdinates(PG_FUNCTION_ARGS)
+{
+  GSERIALIZED *in;
+  GSERIALIZED *out;
+  LWGEOM *lwgeom;
+  const char *ospec;
+  LWORD o1, o2;
+
+  ospec = PG_GETARG_CSTRING(1);
+  if ( strlen(ospec) != 2 )
+  {
+    lwerror("Invalid ordinate specification. "
+            "Need two letters from the set (x,y,z,m). "
+            "Got '%s'", ospec);
+    PG_RETURN_NULL();
+  }
+  o1 = ordname2ordval( ospec[0] );
+  o2 = ordname2ordval( ospec[1] );
+
+  in = (GSERIALIZED *)PG_DETOAST_DATUM_COPY(PG_GETARG_DATUM(0));
+
+  /* Check presence of given ordinates */
+  if ( ( o1 == LWORD_M || o2 == LWORD_M ) && ! gserialized_has_m(in) )
+  {
+    lwerror("Geometry does not have an M ordinate");
+    PG_RETURN_NULL();
+  }
+  if ( ( o1 == LWORD_Z || o2 == LWORD_Z ) && ! gserialized_has_z(in) )
+  {
+    lwerror("Geometry does not have a Z ordinate");
+    PG_RETURN_NULL();
+  }
+
+  /* Nothing to do if swapping the same ordinate, pity for the copy... */
+  if ( o1 == o2 ) PG_RETURN_POINTER(in);
+
+  lwgeom = lwgeom_from_gserialized(in);
+  lwgeom_swap_ordinates(lwgeom, o1, o2);
+  out = geometry_serialize(lwgeom);
+  lwgeom_free(lwgeom);
+  PG_FREE_IF_COPY(in, 0);
+  PG_RETURN_POINTER(out);
 }
index 654b7c010d0ede6b9d89822013d184eb1bd50762..825bc63b981eefe74ffba564031f74cb3b4fe491 100644 (file)
@@ -4706,6 +4706,15 @@ CREATE OR REPLACE FUNCTION ST_DFullyWithin(geom1 geometry, geom2 geometry, float
        AS 'SELECT $1 && ST_Expand($2,$3) AND $2 && ST_Expand($1,$3) AND _ST_DFullyWithin(ST_ConvexHull($1), ST_ConvexHull($2), $3)'
        LANGUAGE 'sql' IMMUTABLE; 
        
+-- Availability: 2.2.0
+CREATE OR REPLACE FUNCTION ST_SwapOrdinates(geom geometry, ords cstring)
+       RETURNS geometry
+       AS 'MODULE_PATHNAME', 'ST_SwapOrdinates'
+       LANGUAGE 'c' IMMUTABLE STRICT;
+
+-- NOTE: same as ST_SwapOrdinates(geometry, 'xy')
+--       but slightly faster in that it doesn't need to parse ordinate
+--       spec strings
 CREATE OR REPLACE FUNCTION ST_FlipCoordinates(geometry)
        RETURNS geometry
        AS 'MODULE_PATHNAME', 'ST_FlipCoordinates'
index 435f53343ddabe6bac5c2226ca92bdbc7ecb1554..d34211d210baab081706f4457d319bd39e47b7b1 100644 (file)
@@ -129,6 +129,7 @@ TESTS = \
        typmod \
        remove_repeated_points \
        split \
+       swapordinates \
        relate \
        bestsrid \
        concave_hull\
diff --git a/regress/swapordinates.sql b/regress/swapordinates.sql
new file mode 100644 (file)
index 0000000..382a2b6
--- /dev/null
@@ -0,0 +1,15 @@
+SELECT 'flip1', ST_AsText(ST_FlipCoordinates('POINT(0 1)'));
+SELECT 'flip2', ST_AsText(ST_FlipCoordinates('GEOMETRYCOLLECTION(POINT(1 2),MULTIPOLYGON(((0 1,1 2,2 1,0 1)),((10 0,20 0,20 10,10 0),(2 2,4 2,4 4,2 2))),LINESTRING(0 1,1 0))'));
+
+-- Bogus calls (swapping unavailable ordinate)
+SELECT ST_AsText(ST_SwapOrdinates('POINTZ(0 1 2)','xm'));
+SELECT ST_AsText(ST_SwapOrdinates('POINTM(0 1 2)','zy'));
+-- Bogus calls (short spec)
+SELECT ST_AsText(ST_SwapOrdinates('POINTZ(0 1 2)','x'));
+-- Bogus calls (invalid ordinate names)
+SELECT ST_AsText(ST_SwapOrdinates('POINTZ(0 1 2)','pq'));
+
+SELECT 'swap1', ST_AsText(ST_SwapOrdinates('POINTZ(0 1 2)','xz'));
+SELECT 'swap2', ST_AsText(ST_SwapOrdinates('POINTM(0 1 2)','my'));
+SELECT 'swap3', ST_AsText(ST_SwapOrdinates('POINTZM(0 1 2 3)','mz'));
+SELECT 'swap4', ST_AsText(ST_SwapOrdinates('MULTICURVE ZM ((5 5 1 3, 3 5 2 2, 3 3 3 1, 0 3 1 1), CIRCULARSTRING ZM (0 0 0 0, 0.2 1 3 -2, 0.5 1.4 1 2), COMPOUNDCURVE ZM (CIRCULARSTRING ZM (0 0 0 0,1 1 1 2,1 0 0 1),(1 0 0 1,0 1 5 4)))','my'));
diff --git a/regress/swapordinates_expected b/regress/swapordinates_expected
new file mode 100644 (file)
index 0000000..c498bba
--- /dev/null
@@ -0,0 +1,10 @@
+flip1|POINT(1 0)
+flip2|GEOMETRYCOLLECTION(POINT(2 1),MULTIPOLYGON(((1 0,2 1,1 2,1 0)),((0 10,0 20,10 20,0 10),(2 2,2 4,4 4,2 2))),LINESTRING(1 0,0 1))
+ERROR:  Geometry does not have an M ordinate
+ERROR:  Geometry does not have a Z ordinate
+ERROR:  Invalid ordinate specification. Need two letters from the set (x,y,z,m). Got 'x'
+ERROR:  Invalid ordinate name 'p'. Expected x,y,z or m
+swap1|POINT Z (2 1 0)
+swap2|POINT M (0 2 1)
+swap3|POINT ZM (0 1 3 2)
+swap4|MULTICURVE ZM ((5 3 1 5,3 2 2 5,3 1 3 3,0 1 1 3),CIRCULARSTRING ZM (0 0 0 0,0.2 -2 3 1,0.5 2 1 1.4),COMPOUNDCURVE ZM (CIRCULARSTRING ZM (0 0 0 0,1 2 1 1,1 1 0 0),(1 1 0 0,0 4 5 1)))