From: Sandro Santilli Date: Wed, 25 Feb 2015 15:03:31 +0000 (+0000) Subject: Add ST_SwapOrdinates function X-Git-Tag: 2.2.0rc1~631 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=f89961fc6eaa65cc7dcc936a8e3e236095173a71;p=postgis Add ST_SwapOrdinates function This is a generalization of ST_FlipCoordinates git-svn-id: http://svn.osgeo.org/postgis/trunk@13289 b70326c6-7e19-0410-871a-916f4a2858ee --- diff --git a/NEWS b/NEWS index b9b4ae18d..7faa37285 100644 --- 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) diff --git a/doc/reference_processing.xml b/doc/reference_processing.xml index 357340ac6..a5a9e2697 100644 --- a/doc/reference_processing.xml +++ b/doc/reference_processing.xml @@ -1606,6 +1606,73 @@ POINT(2 1) ]]> + + + See Also + + + + + + + + ST_SwapOrdinates + Returns a version of the given geometry with + given ordinate values swapped. + + + + + + + geometry ST_SwapOrdinates + geometry geom + cstring ords + + + + + + Description + +Returns a version of the given geometry with given ordinates swapped. + + +The ords parameter is a 2-characters string naming +the ordinates to swap. Valid names are: x,y,z and m. + + &curve_support; + &Z_support; + &M_support; + Availability: 2.2.0 + &P_support; + &T_support; + + + + Example + + + + + + See Also + + + diff --git a/liblwgeom/liblwgeom.h.in b/liblwgeom/liblwgeom.h.in index 0c0b80706..4fc474b6b 100644 --- a/liblwgeom/liblwgeom.h.in +++ b/liblwgeom/liblwgeom.h.in @@ -84,6 +84,14 @@ #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); diff --git a/liblwgeom/liblwgeom_internal.h b/liblwgeom/liblwgeom_internal.h index a1eafdb7f..ab9fc446d 100644 --- a/liblwgeom/liblwgeom_internal.h +++ b/liblwgeom/liblwgeom_internal.h @@ -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? */ diff --git a/liblwgeom/lwgeom.c b/liblwgeom/lwgeom.c index 07faeaf23..23efa18f6 100644 --- a/liblwgeom/lwgeom.c +++ b/liblwgeom/lwgeom.c @@ -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; inrings; 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; ingeoms; 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) diff --git a/liblwgeom/ptarray.c b/liblwgeom/ptarray.c index a734c68d8..44a7f1ab5 100644 --- a/liblwgeom/ptarray.c +++ b/liblwgeom/ptarray.c @@ -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 diff --git a/postgis/lwgeom_functions_basic.c b/postgis/lwgeom_functions_basic.c index c78c6d61c..b7038763a 100644 --- a/postgis/lwgeom_functions_basic.c +++ b/postgis/lwgeom_functions_basic.c @@ -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); } diff --git a/postgis/postgis.sql.in b/postgis/postgis.sql.in index 654b7c010..825bc63b9 100644 --- a/postgis/postgis.sql.in +++ b/postgis/postgis.sql.in @@ -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' diff --git a/regress/Makefile.in b/regress/Makefile.in index 435f53343..d34211d21 100644 --- a/regress/Makefile.in +++ b/regress/Makefile.in @@ -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 index 000000000..382a2b6e6 --- /dev/null +++ b/regress/swapordinates.sql @@ -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 index 000000000..c498bba96 --- /dev/null +++ b/regress/swapordinates_expected @@ -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)))