From c86d9084bde2723629b28fb8f2600e1b55ca8383 Mon Sep 17 00:00:00 2001
From: Sandro Santilli <strk@keybit.net>
Date: Wed, 14 Aug 2013 22:40:16 +0000
Subject: [PATCH] Implement ST_ForceCurve (#2430)

git-svn-id: http://svn.osgeo.org/postgis/trunk@11803 b70326c6-7e19-0410-871a-916f4a2858ee
---
 NEWS                             |  2 ++
 liblwgeom/cunit/cu_libgeom.c     | 56 ++++++++++++++++++++++++++++++++
 liblwgeom/liblwgeom.h.in         |  7 ++++
 liblwgeom/lwcompound.c           |  9 +++++
 liblwgeom/lwgeom.c               | 48 +++++++++++++++++++++++++--
 libpgcommon/lwgeom_pg.h          |  1 +
 postgis/lwgeom_functions_basic.c | 23 +++++++++++++
 postgis/postgis.sql.in           |  6 ++++
 regress/Makefile.in              |  1 +
 regress/forcecurve.sql           | 12 +++++++
 regress/forcecurve_expected      | 12 +++++++
 11 files changed, 175 insertions(+), 2 deletions(-)
 create mode 100644 regress/forcecurve.sql
 create mode 100644 regress/forcecurve_expected

diff --git a/NEWS b/NEWS
index cd08fd79a..e28c707d0 100644
--- a/NEWS
+++ b/NEWS
@@ -10,6 +10,8 @@ PostGIS 2.2.0
 
  * New Features *
 
+  - #2430, ST_ForceCurve
+
  * Enhancements *
 
   - Added missing variants of ST_TPI(), ST_TRI() and ST_Roughness()
diff --git a/liblwgeom/cunit/cu_libgeom.c b/liblwgeom/cunit/cu_libgeom.c
index ae0e319f5..18c8deeba 100644
--- a/liblwgeom/cunit/cu_libgeom.c
+++ b/liblwgeom/cunit/cu_libgeom.c
@@ -891,6 +891,61 @@ static void test_lwgeom_same(void)
 
 }
 
+/*
+ * Test lwgeom_force_curve
+ */
+static void test_lwgeom_as_curve(void)
+{
+	LWGEOM *geom;
+	LWGEOM *geom2;
+	char *in_ewkt, *out_ewkt;
+
+	geom = lwgeom_from_wkt("LINESTRING(0 0, 10 0)", LW_PARSER_CHECK_NONE);
+	geom2 = lwgeom_as_curve(geom);
+	in_ewkt = "COMPOUNDCURVE((0 0,10 0))";
+	out_ewkt = lwgeom_to_ewkt(geom2);
+	if (strcmp(in_ewkt, out_ewkt))
+		fprintf(stderr, "\nExp:   %s\nObt:  %s\n", in_ewkt, out_ewkt);
+	CU_ASSERT_STRING_EQUAL(in_ewkt, out_ewkt);
+	lwfree(out_ewkt);
+	lwgeom_free(geom);
+	lwgeom_free(geom2);
+
+	geom = lwgeom_from_wkt("MULTILINESTRING((0 0, 10 0))", LW_PARSER_CHECK_NONE);
+	geom2 = lwgeom_as_curve(geom);
+	in_ewkt = "MULTICURVE((0 0,10 0))";
+	out_ewkt = lwgeom_to_ewkt(geom2);
+	if (strcmp(in_ewkt, out_ewkt))
+		fprintf(stderr, "\nExp:   %s\nObt:  %s\n", in_ewkt, out_ewkt);
+	CU_ASSERT_STRING_EQUAL(in_ewkt, out_ewkt);
+	lwfree(out_ewkt);
+	lwgeom_free(geom);
+	lwgeom_free(geom2);
+
+	geom = lwgeom_from_wkt("POLYGON((0 0, 10 0, 10 10, 0 10, 0 0))", LW_PARSER_CHECK_NONE);
+	geom2 = lwgeom_as_curve(geom);
+	in_ewkt = "CURVEPOLYGON((0 0,10 0,10 10,0 10,0 0))";
+	out_ewkt = lwgeom_to_ewkt(geom2);
+	if (strcmp(in_ewkt, out_ewkt))
+		fprintf(stderr, "\nExp:   %s\nObt:  %s\n", in_ewkt, out_ewkt);
+	CU_ASSERT_STRING_EQUAL(in_ewkt, out_ewkt);
+	lwfree(out_ewkt);
+	lwgeom_free(geom);
+	lwgeom_free(geom2);
+
+	geom = lwgeom_from_wkt("MULTIPOLYGON(((0 0, 10 0, 10 10, 0 10, 0 0)))", LW_PARSER_CHECK_NONE);
+	geom2 = lwgeom_as_curve(geom);
+	in_ewkt = "MULTISURFACE(((0 0,10 0,10 10,0 10,0 0)))";
+	out_ewkt = lwgeom_to_ewkt(geom2);
+	if (strcmp(in_ewkt, out_ewkt))
+		fprintf(stderr, "\nExp:   %s\nObt:  %s\n", in_ewkt, out_ewkt);
+	CU_ASSERT_STRING_EQUAL(in_ewkt, out_ewkt);
+	lwfree(out_ewkt);
+	lwgeom_free(geom);
+	lwgeom_free(geom2);
+
+}
+
 /*
 ** Used by test harness to register the tests in this file.
 */
@@ -914,6 +969,7 @@ CU_TestInfo libgeom_tests[] =
 	PG_TEST(test_lwgeom_calculate_gbox),
 	PG_TEST(test_lwgeom_is_empty),
 	PG_TEST(test_lwgeom_same),
+	PG_TEST(test_lwgeom_as_curve),
 	CU_TEST_INFO_NULL
 };
 CU_SuiteInfo libgeom_suite = {"libgeom",  NULL,  NULL, libgeom_tests};
diff --git a/liblwgeom/liblwgeom.h.in b/liblwgeom/liblwgeom.h.in
index 688e7830c..5d3ff05d8 100644
--- a/liblwgeom/liblwgeom.h.in
+++ b/liblwgeom/liblwgeom.h.in
@@ -580,6 +580,7 @@ extern LWPSURFACE *lwgeom_as_lwpsurface(const LWGEOM *lwgeom);
 extern LWTRIANGLE *lwgeom_as_lwtriangle(const LWGEOM *lwgeom);
 extern LWTIN *lwgeom_as_lwtin(const LWGEOM *lwgeom);
 extern LWGEOM *lwgeom_as_multi(const LWGEOM *lwgeom);
+extern LWGEOM *lwgeom_as_curve(const LWGEOM *lwgeom);
 
 /* Casts LW*->LWGEOM (always cast) */
 extern LWGEOM *lwtin_as_lwgeom(const LWTIN *obj);
@@ -946,6 +947,12 @@ extern int lwcurvepoly_add_ring(LWCURVEPOLY *poly, LWGEOM *ring);
 */
 extern int lwcompound_add_lwgeom(LWCOMPOUND *comp, LWGEOM *geom);
 
+/**
+* Construct an equivalent compound curve from a linestring. 
+* Compound curves can have linear components, so this works fine 
+*/
+extern LWCOMPOUND* lwcompound_construct_from_lwline(const LWLINE *lwpoly);
+
 /**
 * Construct an equivalent curve polygon from a polygon. Curve polygons
 * can have linear rings as their rings, so this works fine (in theory?)
diff --git a/liblwgeom/lwcompound.c b/liblwgeom/lwcompound.c
index 8ff92e4cf..a61374693 100644
--- a/liblwgeom/lwcompound.c
+++ b/liblwgeom/lwcompound.c
@@ -191,3 +191,12 @@ lwcompound_contains_point(const LWCOMPOUND *comp, const POINT2D *pt)
 	/* Inside */
 	return LW_INSIDE;
 }	
+
+LWCOMPOUND *
+lwcompound_construct_from_lwline(const LWLINE *lwline)
+{
+  LWCOMPOUND* ogeom = lwcompound_construct_empty(lwline->srid, FLAGS_GET_Z(lwline->flags), FLAGS_GET_M(lwline->flags));
+  lwcompound_add_lwgeom(ogeom, lwgeom_clone((LWGEOM*)lwline));
+	/* ogeom->bbox = lwline->bbox; */
+  return ogeom;
+}
diff --git a/liblwgeom/lwgeom.c b/liblwgeom/lwgeom.c
index 55a5ccd56..4221f1c47 100644
--- a/liblwgeom/lwgeom.c
+++ b/liblwgeom/lwgeom.c
@@ -261,7 +261,7 @@ LWGEOM *lwpoint_as_lwgeom(const LWPOINT *obj)
 /**
 ** Look-up for the correct MULTI* type promotion for singleton types.
 */
-static uint8_t MULTITYPE[17] =
+static uint8_t MULTITYPE[NUMTYPES] =
 {
 	0,
 	MULTIPOINTTYPE,        /*  1 */
@@ -274,7 +274,7 @@ static uint8_t MULTITYPE[17] =
 	POLYHEDRALSURFACETYPE, /* 11 */
 	0, 0,
 	TINTYPE,               /* 14 */
-	0,0
+	0
 };
 
 /**
@@ -317,6 +317,50 @@ lwgeom_as_multi(const LWGEOM *lwgeom)
 	return ogeom;
 }
 
+/**
+* Create a new LWGEOM of the appropriate CURVE* type.
+*/
+LWGEOM *
+lwgeom_as_curve(const LWGEOM *lwgeom)
+{
+	LWGEOM *ogeom;
+	int type = lwgeom->type;
+/*
+  int hasz = FLAGS_GET_Z(lwgeom->flags);
+  int hasm = FLAGS_GET_M(lwgeom->flags);
+  int srid = lwgeom->srid;
+*/
+
+	switch(type)
+	{
+		case LINETYPE:
+      /* turn to COMPOUNDCURVE */
+      ogeom = (LWGEOM*)lwcompound_construct_from_lwline((LWLINE*)lwgeom);
+      break;
+		case POLYGONTYPE:
+      ogeom = (LWGEOM*)lwcurvepoly_construct_from_lwpoly(lwgeom_as_lwpoly(lwgeom));
+      break;
+		case MULTILINETYPE:
+      /* turn to MULTICURVE */
+      ogeom = lwgeom_clone(lwgeom);
+      ogeom->type = MULTICURVETYPE;
+      break;
+		case MULTIPOLYGONTYPE:
+      /* turn to MULTISURFACE */
+      ogeom = lwgeom_clone(lwgeom);
+      ogeom->type = MULTISURFACETYPE;
+      break;
+		case COLLECTIONTYPE:
+		default:
+      ogeom = lwgeom_clone(lwgeom);
+      break;
+  }
+
+  /* TODO: copy bbox from input geom ? */
+
+  return ogeom;
+}
+
 
 /**
 * Free the containing LWGEOM and the associated BOX. Leave the underlying 
diff --git a/libpgcommon/lwgeom_pg.h b/libpgcommon/lwgeom_pg.h
index 3d392e84f..90526be47 100644
--- a/libpgcommon/lwgeom_pg.h
+++ b/libpgcommon/lwgeom_pg.h
@@ -150,6 +150,7 @@ Datum LWGEOM_force_3dz(PG_FUNCTION_ARGS);
 Datum LWGEOM_force_4d(PG_FUNCTION_ARGS);
 Datum LWGEOM_force_collection(PG_FUNCTION_ARGS);
 Datum LWGEOM_force_multi(PG_FUNCTION_ARGS);
+Datum LWGEOM_force_curve(PG_FUNCTION_ARGS);
 
 Datum LWGEOMFromWKB(PG_FUNCTION_ARGS);
 Datum WKBFromLWGEOM(PG_FUNCTION_ARGS);
diff --git a/postgis/lwgeom_functions_basic.c b/postgis/lwgeom_functions_basic.c
index bb975e991..2e5727b24 100644
--- a/postgis/lwgeom_functions_basic.c
+++ b/postgis/lwgeom_functions_basic.c
@@ -519,6 +519,29 @@ Datum LWGEOM_force_multi(PG_FUNCTION_ARGS)
 	PG_RETURN_POINTER(result);
 }
 
+/** transform input geometry to a curved type */
+PG_FUNCTION_INFO_V1(LWGEOM_force_curve);
+Datum LWGEOM_force_curve(PG_FUNCTION_ARGS)
+{
+	GSERIALIZED *geom = (GSERIALIZED *)PG_DETOAST_DATUM(PG_GETARG_DATUM(0));
+	GSERIALIZED *result;
+	LWGEOM *lwgeom;
+	LWGEOM *ogeom;
+
+	POSTGIS_DEBUG(2, "LWGEOM_force_curve called");
+
+  /* TODO: early out if input is already a curve */
+
+	lwgeom = lwgeom_from_gserialized(geom);
+	ogeom = lwgeom_as_curve(lwgeom);
+
+	result = geometry_serialize(ogeom);
+
+	PG_FREE_IF_COPY(geom, 0);
+
+	PG_RETURN_POINTER(result);
+}
+
 /** transform input geometry to a SFS 1.1 geometry type compliant */
 PG_FUNCTION_INFO_V1(LWGEOM_force_sfs);
 Datum LWGEOM_force_sfs(PG_FUNCTION_ARGS)
diff --git a/postgis/postgis.sql.in b/postgis/postgis.sql.in
index 42d871dc6..f5c3d1c1c 100644
--- a/postgis/postgis.sql.in
+++ b/postgis/postgis.sql.in
@@ -1277,6 +1277,12 @@ CREATE OR REPLACE FUNCTION ST_Multi(geometry)
 	AS 'MODULE_PATHNAME', 'LWGEOM_force_multi'
 	LANGUAGE 'c' IMMUTABLE STRICT;
 
+-- Availability: 2.2.0
+CREATE OR REPLACE FUNCTION ST_ForceCurve(geometry)
+	RETURNS geometry
+	AS 'MODULE_PATHNAME', 'LWGEOM_force_curve'
+	LANGUAGE 'c' IMMUTABLE STRICT;
+
 -- Availability: 2.1.0
 CREATE OR REPLACE FUNCTION ST_ForceSFS(geometry)
 	RETURNS geometry
diff --git a/regress/Makefile.in b/regress/Makefile.in
index 06c723eee..d975a518f 100644
--- a/regress/Makefile.in
+++ b/regress/Makefile.in
@@ -81,6 +81,7 @@ TESTS = \
 	summary \
 	affine \
 	empty \
+  forcecurve \
 	measures \
 	legacy \
 	long_xact \
diff --git a/regress/forcecurve.sql b/regress/forcecurve.sql
new file mode 100644
index 000000000..b655ba36e
--- /dev/null
+++ b/regress/forcecurve.sql
@@ -0,0 +1,12 @@
+SELECT '1', ST_AsText(ST_ForceCurve('POINT(0 0)'));
+SELECT '2', ST_AsText(ST_ForceCurve('MULTIPOINT((0 0))'));
+SELECT '3', ST_AsText(ST_ForceCurve('LINESTRING(0 0, 10 0)'));
+SELECT '4', ST_AsText(ST_ForceCurve('MULTILINESTRING((0 0, 10 0),(30 0, 30 2))'));
+SELECT '5', ST_AsText(ST_ForceCurve('POLYGON((0 0,10 0,10 10,0 10,0 0))'));
+SELECT '6', ST_AsText(ST_ForceCurve('MULTIPOLYGON(((0 0,10 0,10 10,0 10,0 0)))'));
+SELECT '7', ST_AsText(ST_ForceCurve('GEOMETRYCOLLECTION(MULTIPOLYGON(((0 0,10 0,10 10,0 10,0 0))))'));
+SELECT '8', ST_AsText(ST_ForceCurve('CIRCULARSTRING(0 0,10 1,20 0)'));
+SELECT '9', ST_AsText(ST_ForceCurve('COMPOUNDCURVE(CIRCULARSTRING(0 0,10 1,20 0))'));
+SELECT '10', ST_AsText(ST_ForceCurve('MULTICURVE(COMPOUNDCURVE(CIRCULARSTRING(0 0,10 1,20 0)))'));
+SELECT '11', ST_AsText(ST_ForceCurve('CURVEPOLYGON(COMPOUNDCURVE(CIRCULARSTRING(0 0,10 1,20 0),(20 0,0 0)))'));
+SELECT '12', ST_AsText(ST_ForceCurve('MULTISURFACE(CURVEPOLYGON(COMPOUNDCURVE(CIRCULARSTRING(0 0,10 1,20 0),(20 0,0 0))))'));
diff --git a/regress/forcecurve_expected b/regress/forcecurve_expected
new file mode 100644
index 000000000..e4e784627
--- /dev/null
+++ b/regress/forcecurve_expected
@@ -0,0 +1,12 @@
+1|POINT(0 0)
+2|MULTIPOINT(0 0)
+3|COMPOUNDCURVE((0 0,10 0))
+4|MULTICURVE((0 0,10 0),(30 0,30 2))
+5|CURVEPOLYGON((0 0,10 0,10 10,0 10,0 0))
+6|MULTISURFACE(((0 0,10 0,10 10,0 10,0 0)))
+7|GEOMETRYCOLLECTION(MULTIPOLYGON(((0 0,10 0,10 10,0 10,0 0))))
+8|CIRCULARSTRING(0 0,10 1,20 0)
+9|COMPOUNDCURVE(CIRCULARSTRING(0 0,10 1,20 0))
+10|MULTICURVE(COMPOUNDCURVE(CIRCULARSTRING(0 0,10 1,20 0)))
+11|CURVEPOLYGON(COMPOUNDCURVE(CIRCULARSTRING(0 0,10 1,20 0),(20 0,0 0)))
+12|MULTISURFACE(CURVEPOLYGON(COMPOUNDCURVE(CIRCULARSTRING(0 0,10 1,20 0),(20 0,0 0))))
-- 
2.40.0