[postgis-tickets] r15430 - Implement extended ST_CurveToLine signature

Sandro Santilli strk at kbt.io
Mon Jun 19 09:07:00 PDT 2017


Author: strk
Date: 2017-06-19 09:06:59 -0700 (Mon, 19 Jun 2017)
New Revision: 15430

Added:
   trunk/liblwgeom/cunit/cu_lwstroke.c
   trunk/regress/curvetoline.sql
   trunk/regress/curvetoline_expected
Modified:
   trunk/doc/reference_processing.xml
   trunk/liblwgeom/cunit/Makefile.in
   trunk/liblwgeom/cunit/cu_tester.c
   trunk/liblwgeom/liblwgeom.h.in
   trunk/liblwgeom/liblwgeom_internal.h
   trunk/liblwgeom/lwstroke.c
   trunk/postgis/lwgeom_sqlmm.c
   trunk/postgis/postgis.sql.in
   trunk/regress/Makefile.in
   trunk/regress/sql-mm-circularstring.sql
Log:
Implement extended ST_CurveToLine signature

Adds lwcurve_linearize function at liblwgeom level.
Turns lwgeom_stroke into a wrapper, keept it for backward compatibility.
Reduces allocations in linearization procedures.

Implements SYMMETRIC and RETAIN_ANGLE flags.
Implements MAX_DEVIATION, MAX_ANGLE and SEGS_PER_QUADRANT configs.

Includes unit and SQL tests.
Includes documentation.

Closes #2464 (maxError configuration is MAX_DEVIATION)
Closes #3772 (balanced output is SYMMETRIC and RETAIN_ANGLE flags)

Document the new ST_CurveToLine signature

Modified: trunk/doc/reference_processing.xml
===================================================================
--- trunk/doc/reference_processing.xml	2017-06-13 13:29:29 UTC (rev 15429)
+++ trunk/doc/reference_processing.xml	2017-06-19 16:06:59 UTC (rev 15430)
@@ -845,6 +845,13 @@
 			<paramdef><type>geometry</type> <parameter>curveGeom</parameter></paramdef>
 			<paramdef><type>integer</type> <parameter>segments_per_qtr_circle</parameter></paramdef>
 		  </funcprototype>
+		  <funcprototype>
+			<funcdef>geometry <function>ST_CurveToLine</function></funcdef>
+			<paramdef><type>geometry</type> <parameter>curveGeom</parameter></paramdef>
+			<paramdef><type>float</type> <parameter>tolerance</parameter></paramdef>
+			<paramdef><type>integer</type> <parameter>tolerance_type</parameter></paramdef>
+			<paramdef><type>integer</type> <parameter>flags</parameter></paramdef>
+		  </funcprototype>
 		</funcsynopsis>
 	  </refsynopsisdiv>
 
@@ -852,9 +859,44 @@
 		<title>Description</title>
 
 		<para>Converst a CIRCULAR STRING to regular LINESTRING or CURVEPOLYGON to POLYGON. Useful for outputting to devices that can't support CIRCULARSTRING geometry types</para>
+
 		<para>Converts a given geometry to a linear geometry.
-		Each curved geometry or segment is converted into a linear approximation using the default value of 32 segments per quarter circle</para>
+		Each curved geometry or segment is converted into a linear
+approximation using the given `tolerance` and options (32 segments per
+quadrant and no options by default).</para>
+
+    <para>
+The 'tolerance_type' argument determines interpretation of the
+`tolerance` argument. It can take the following values:
+      <itemizedlist>
+        <listitem>
+          <para>0 (default): Tolerance is max segments per quadrant.</para>
+        </listitem>
+        <listitem>
+          <para>1: Tolerance is max-deviation of line from curve, in source units.</para>
+        </listitem>
+        <listitem>
+          <para>2: Tolerance is max-angle, in radians, between generating radii.</para>
+        </listitem>
+      </itemizedlist>
+    </para>
+
+    <para>
+The 'flags' argument is a bitfield. 0 by default.
+Supported bits are:
+      <itemizedlist>
+        <listitem>
+          <para>1: Symmetric (orientation idependent) output.</para>
+        </listitem>
+        <listitem>
+          <para>2: Retain angle, avoids reducing angles (segment lengths) when producing symmetric output. Has no effect when Symmetric flag is off.</para>
+        </listitem>
+      </itemizedlist>
+    </para>
+
 		<para>Availability: 1.2.2?</para>
+    <para>Changed: 2.4.0 added support for max-deviation and max-angle tolerance, and for symmetric output.</para>
+
 		<para>&sfs_compliant;</para>
 		<para>&sqlmm_compliant; SQL-MM 3: 7.1.7</para>
 		<para>&Z_support;</para>
@@ -921,7 +963,19 @@
  220244.779251566 150505.61834893,220207.243902439 150496,220187.50360229 150462.657300346,
  220197.12195122 150425.12195122,220227 150406)
 
+-- Ensure approximated line is no further than 20 units away from
+-- original curve, and make the result direction-neutral
+SELECT ST_AsText(ST_CurveToLine(
+ 'CIRCULARSTRING(0 0,100 -100,200 0)'::geometry,
+	20, -- Tolerance
+	1, -- Above is max distance between curve and line
+	1  -- Symmetric flag
+));
+st_astext
+-------------------------------------------------------------------------------------------
+ LINESTRING(0 0,50 -86.6025403784438,150 -86.6025403784439,200 -1.1331077795296e-13,200 0)
 
+
 		</programlisting>
 	  </refsection>
 

Modified: trunk/liblwgeom/cunit/Makefile.in
===================================================================
--- trunk/liblwgeom/cunit/Makefile.in	2017-06-13 13:29:29 UTC (rev 15429)
+++ trunk/liblwgeom/cunit/Makefile.in	2017-06-19 16:06:59 UTC (rev 15430)
@@ -39,6 +39,7 @@
 	cu_node.o \
 	cu_clip_by_rect.o \
 	cu_libgeom.o \
+	cu_lwstroke.o \
 	cu_split.o \
 	cu_stringbuffer.o \
 	cu_triangulate.o \

Added: trunk/liblwgeom/cunit/cu_lwstroke.c
===================================================================
--- trunk/liblwgeom/cunit/cu_lwstroke.c	                        (rev 0)
+++ trunk/liblwgeom/cunit/cu_lwstroke.c	2017-06-19 16:06:59 UTC (rev 15430)
@@ -0,0 +1,253 @@
+/**********************************************************************
+ *
+ * PostGIS - Spatial Types for PostgreSQL
+ * http://postgis.net
+ *
+ * Copyright (C) 2017 Sandro Santilli <strk at 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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h> /* for M_PI */
+#include "CUnit/Basic.h"
+#include "CUnit/CUnit.h"
+
+#include "liblwgeom_internal.h"
+#include "cu_tester.h"
+
+
+static LWGEOM* lwgeom_from_text(const char *str)
+{
+	LWGEOM_PARSER_RESULT r;
+	if( LW_FAILURE == lwgeom_parse_wkt(&r, (char*)str, LW_PARSER_CHECK_NONE) )
+		return NULL;
+	return r.geom;
+}
+
+static char* lwgeom_to_text(const LWGEOM *geom, int prec)
+{
+	gridspec grid;
+	LWGEOM *gridded;
+	char *wkt;
+
+	memset(&grid, 0, sizeof(gridspec));
+	grid.xsize = prec;
+	grid.ysize = prec;
+	gridded = lwgeom_grid(geom, &grid);
+
+	wkt = lwgeom_to_wkt(gridded, WKT_ISO, 15, NULL);
+	lwgeom_free(gridded);
+	return wkt;
+}
+
+static void test_lwcurve_linearize(void)
+{
+	LWGEOM *in;
+	LWGEOM *out;
+	char *str;
+	int toltype;
+
+	/***********************************************************
+	 *
+	 *  Segments per quadrant tolerance type
+	 *
+	 ***********************************************************/
+
+	toltype = LW_LINEARIZE_TOLERANCE_TYPE_SEGS_PER_QUAD;
+
+	/* 2 quadrants arc (180 degrees, PI radians) */
+	in = lwgeom_from_text("CIRCULARSTRING(0 0,100 100,200 0)");
+	/* 2 segment per quadrant */
+	out = lwcurve_linearize(in, 2, toltype, 0);
+	str = lwgeom_to_text(out, 2);
+	ASSERT_STRING_EQUAL(str, "LINESTRING(0 0,30 70,100 100,170 70,200 0)");
+	lwfree(str);
+	lwgeom_free(out);
+	/* 3 segment per quadrant */
+	out = lwcurve_linearize(in, 3, toltype, 0);
+	str = lwgeom_to_text(out, 2);
+	ASSERT_STRING_EQUAL(str, "LINESTRING(0 0,14 50,50 86,100 100,150 86,186 50,200 0)");
+	lwfree(str);
+	lwgeom_free(out);
+	/* 3.5 segment per quadrant (invalid) */
+	cu_error_msg_reset();
+	out = lwcurve_linearize(in, 3.5, toltype, 0);
+	CU_ASSERT( out == NULL );
+	ASSERT_STRING_EQUAL(cu_error_msg, "lwarc_linearize: segments per quadrant must be an integer value, got 3.5");
+	lwgeom_free(out);
+	/* -2 segment per quadrant (invalid) */
+	cu_error_msg_reset();
+	out = lwcurve_linearize(in, -2, toltype, 0);
+	CU_ASSERT( out == NULL );
+	ASSERT_STRING_EQUAL(cu_error_msg, "lwarc_linearize: segments per quadrant must be at least 1, got -2");
+	lwgeom_free(out);
+	/* 0 segment per quadrant (invalid) */
+	cu_error_msg_reset();
+	out = lwcurve_linearize(in, 0, toltype, 0);
+	CU_ASSERT( out == NULL );
+	ASSERT_STRING_EQUAL(cu_error_msg, "lwarc_linearize: segments per quadrant must be at least 1, got 0");
+	lwgeom_free(out);
+	lwgeom_free(in);
+
+	/* 1.5 quadrants arc (145 degrees - PI*3/4 radians ) */
+	in = lwgeom_from_text("CIRCULARSTRING(29.2893218813453 70.7106781186548,100 100,200 0)");
+	/* 2 segment per quadrant */
+	out = lwcurve_linearize(in, 2, toltype, 0);
+	str = lwgeom_to_text(out, 2);
+	ASSERT_STRING_EQUAL(str, "LINESTRING(30 70,100 100,170 70,200 0)");
+	lwfree(str);
+	lwgeom_free(out);
+	/* 3 segment per quadrant - non-symmetric */
+	out = lwcurve_linearize(in, 3, toltype, 0);
+	str = lwgeom_to_text(out, 2);
+	ASSERT_STRING_EQUAL(str, "LINESTRING(30 70,74 96,126 96,170 70,196 26,200 0)");
+	lwfree(str);
+	lwgeom_free(out);
+	/* 3 segment per quadrant - symmetric */
+	out = lwcurve_linearize(in, 3, toltype, LW_LINEARIZE_FLAG_SYMMETRIC);
+	str = lwgeom_to_text(out, 2);
+	ASSERT_STRING_EQUAL(str, "LINESTRING(30 70,70 96,116 98,158 80,190 46,200 0)");
+	lwfree(str);
+	lwgeom_free(out);
+	/* 3 segment per quadrant - symmetric/retain_angle */
+	out = lwcurve_linearize(in, 3, toltype,
+		LW_LINEARIZE_FLAG_SYMMETRIC |
+		LW_LINEARIZE_FLAG_RETAIN_ANGLE
+	);
+	str = lwgeom_to_text(out, 2);
+	ASSERT_STRING_EQUAL(str, "LINESTRING(30 70,62 92,114 100,160 80,192 38,200 0)");
+	lwfree(str);
+	lwgeom_free(out);
+
+	lwgeom_free(in);
+
+	/***********************************************************
+	 *
+	 *  Maximum deviation tolerance type
+	 *
+	 ***********************************************************/
+
+	toltype = LW_LINEARIZE_TOLERANCE_TYPE_MAX_DEVIATION;
+
+	in = lwgeom_from_text("CIRCULARSTRING(0 0,100 100,200 0)");
+
+	/* Maximum of 10 units of difference, asymmetric */
+	out = lwcurve_linearize(in, 10, toltype, 0);
+	str = lwgeom_to_text(out, 2);
+	ASSERT_STRING_EQUAL(str, "LINESTRING(0 0,38 78,124 98,190 42,200 0)");
+	lwfree(str);
+	lwgeom_free(out);
+	/* Maximum of 0 units of difference (invalid) */
+	cu_error_msg_reset();
+	out = lwcurve_linearize(in, 0, toltype, 0);
+	CU_ASSERT( out == NULL );
+	ASSERT_STRING_EQUAL(cu_error_msg, "lwarc_linearize: max deviation must be bigger than 0, got 0");
+	/* Maximum of -2 units of difference (invalid) */
+	cu_error_msg_reset();
+	out = lwcurve_linearize(in, -2, toltype, 0);
+	CU_ASSERT( out == NULL );
+	ASSERT_STRING_EQUAL(cu_error_msg, "lwarc_linearize: max deviation must be bigger than 0, got -2");
+	/* Maximum of 10 units of difference, symmetric */
+	out = lwcurve_linearize(in, 10, toltype, LW_LINEARIZE_FLAG_SYMMETRIC);
+	str = lwgeom_to_text(out, 2);
+	ASSERT_STRING_EQUAL(str, "LINESTRING(0 0,30 70,100 100,170 70,200 0)");
+	lwfree(str);
+	lwgeom_free(out);
+	/* Maximum of 20 units of difference, asymmetric */
+	out = lwcurve_linearize(in, 20, toltype, 0);
+	str = lwgeom_to_text(out, 2);
+	ASSERT_STRING_EQUAL(str, "LINESTRING(0 0,72 96,184 54,200 0)");
+	lwfree(str);
+	lwgeom_free(out);
+	/* Maximum of 20 units of difference, symmetric */
+	out = lwcurve_linearize(in, 20, toltype, LW_LINEARIZE_FLAG_SYMMETRIC);
+	str = lwgeom_to_text(out, 2);
+	ASSERT_STRING_EQUAL(str, "LINESTRING(0 0,50 86,150 86,200 0)");
+	lwfree(str);
+	lwgeom_free(out);
+	/* Maximum of 20 units of difference, symmetric/retain angle */
+	out = lwcurve_linearize(in, 20, toltype,
+		LW_LINEARIZE_FLAG_SYMMETRIC |
+		LW_LINEARIZE_FLAG_RETAIN_ANGLE
+	);
+	str = lwgeom_to_text(out, 2);
+	ASSERT_STRING_EQUAL(str, "LINESTRING(0 0,40 80,160 80,200 0)");
+	lwfree(str);
+	lwgeom_free(out);
+
+	lwgeom_free(in);
+
+	/***********************************************************
+	 *
+	 *  Maximum angle tolerance type
+	 *
+	 ***********************************************************/
+
+	toltype = LW_LINEARIZE_TOLERANCE_TYPE_MAX_ANGLE;
+
+	in = lwgeom_from_text("CIRCULARSTRING(0 0,100 100,200 0)");
+
+	/* Maximum of 45 degrees, asymmetric */
+	out = lwcurve_linearize(in, M_PI / 4.0, toltype, 0);
+	str = lwgeom_to_text(out, 2);
+	ASSERT_STRING_EQUAL(str, "LINESTRING(0 0,30 70,100 100,170 70,200 0)");
+	lwfree(str);
+	lwgeom_free(out);
+	/* Maximum of 0 degrees (invalid) */
+	cu_error_msg_reset();
+	out = lwcurve_linearize(in, 0, toltype, 0);
+	CU_ASSERT( out == NULL );
+	ASSERT_STRING_EQUAL(cu_error_msg, "lwarc_linearize: max angle must be bigger than 0, got 0");
+	/* Maximum of -2 degrees (invalid) */
+	cu_error_msg_reset();
+	out = lwcurve_linearize(in, -2, toltype, 0);
+	CU_ASSERT( out == NULL );
+	ASSERT_STRING_EQUAL(cu_error_msg, "lwarc_linearize: max angle must be bigger than 0, got -2");
+	/* Maximum of 360 degrees, just return endpoints... */
+	cu_error_msg_reset();
+	out = lwcurve_linearize(in, M_PI*4, toltype, 0);
+	str = lwgeom_to_text(out, 2);
+	ASSERT_STRING_EQUAL(str, "LINESTRING(0 0,200 0)");
+	lwfree(str);
+	lwgeom_free(out);
+	/* Maximum of 70 degrees, asymmetric */
+	out = lwcurve_linearize(in, 70 * M_PI / 180, toltype, 0);
+	str = lwgeom_to_text(out, 2);
+	ASSERT_STRING_EQUAL(str, "LINESTRING(0 0,66 94,176 64,200 0)");
+	lwfree(str);
+	lwgeom_free(out);
+	/* Maximum of 70 degrees, symmetric */
+	out = lwcurve_linearize(in, 70 * M_PI / 180, toltype, LW_LINEARIZE_FLAG_SYMMETRIC);
+	str = lwgeom_to_text(out, 2);
+	ASSERT_STRING_EQUAL(str, "LINESTRING(0 0,50 86,150 86,200 0)");
+	lwfree(str);
+	lwgeom_free(out);
+	/* Maximum of 70 degrees, symmetric/retain angle */
+	out = lwcurve_linearize(in, 70 * M_PI / 180, toltype,
+		LW_LINEARIZE_FLAG_SYMMETRIC |
+		LW_LINEARIZE_FLAG_RETAIN_ANGLE
+	);
+	str = lwgeom_to_text(out, 2);
+	ASSERT_STRING_EQUAL(str, "LINESTRING(0 0,42 82,158 82,200 0)");
+	lwfree(str);
+	lwgeom_free(out);
+
+	lwgeom_free(in);
+
+}
+
+
+/*
+** Used by the test harness to register the tests in this file.
+*/
+void lwstroke_suite_setup(void);
+void lwstroke_suite_setup(void)
+{
+	CU_pSuite suite = CU_add_suite("lwstroke", NULL, NULL);
+	PG_ADD_TEST(suite, test_lwcurve_linearize);
+}

Modified: trunk/liblwgeom/cunit/cu_tester.c
===================================================================
--- trunk/liblwgeom/cunit/cu_tester.c	2017-06-13 13:29:29 UTC (rev 15429)
+++ trunk/liblwgeom/cunit/cu_tester.c	2017-06-19 16:06:59 UTC (rev 15430)
@@ -44,6 +44,7 @@
 extern void iterator_suite_setup(void);
 extern void twkb_in_suite_setup(void);
 extern void libgeom_suite_setup(void);
+extern void lwstroke_suite_setup(void);
 extern void measures_suite_setup(void);
 extern void effectivearea_suite_setup(void);
 extern void minimum_bounding_circle_suite_setup(void);
@@ -93,6 +94,7 @@
     iterator_suite_setup,
 	twkb_in_suite_setup,
 	libgeom_suite_setup,
+	lwstroke_suite_setup,
 	measures_suite_setup,
 	effectivearea_suite_setup,
 	minimum_bounding_circle_suite_setup,

Modified: trunk/liblwgeom/liblwgeom.h.in
===================================================================
--- trunk/liblwgeom/liblwgeom.h.in	2017-06-13 13:29:29 UTC (rev 15429)
+++ trunk/liblwgeom/liblwgeom.h.in	2017-06-19 16:06:59 UTC (rev 15430)
@@ -2158,13 +2158,80 @@
 extern uint8_t* lwgeom_to_twkb_with_idlist(const LWGEOM *geom, int64_t *idlist, uint8_t variant, int8_t precision_xy, int8_t precision_z, int8_t precision_m, size_t *twkb_size);
 
 /*******************************************************************************
- * SQLMM internal functions - TODO: Move into separate header files
+ * SQLMM internal functions
  ******************************************************************************/
 
 int lwgeom_has_arc(const LWGEOM *geom);
 LWGEOM *lwgeom_stroke(const LWGEOM *geom, uint32_t perQuad);
 LWGEOM *lwgeom_unstroke(const LWGEOM *geom);
 
+/**
+ * Semantic of the `tolerance` argument passed to
+ * lwcurve_linearize
+ */
+typedef enum {
+	/**
+	 * Tolerance expresses the number of segments to use
+	 * for each quarter of circle (quadrant). Must be
+	 * an integer.
+	 */
+	LW_LINEARIZE_TOLERANCE_TYPE_SEGS_PER_QUAD = 0,
+	/**
+	 * Tolerance expresses the maximum distance between
+	 * an arbitrary point on the curve and the closest
+	 * point to it on the resulting approximation, in
+	 * cartesian units.
+	 */
+	LW_LINEARIZE_TOLERANCE_TYPE_MAX_DEVIATION = 1,
+	/**
+	 * Tolerance expresses the maximum angle between
+	 * the radii generating approximation line vertices,
+	 * given in radiuses. A value of 1 would result
+	 * in an approximation of a semicircle composed by
+	 * 180 segments
+	 */
+	LW_LINEARIZE_TOLERANCE_TYPE_MAX_ANGLE = 2
+} LW_LINEARIZE_TOLERANCE_TYPE;
+
+typedef enum {
+  /**
+   * Symmetric linearization means that the output
+   * vertices would be the same no matter the order
+   * of the points defining the input curve.
+   */
+	LW_LINEARIZE_FLAG_SYMMETRIC = 1 << 0,
+
+  /**
+   * Retain angle instructs the engine to try its best
+   * to retain the requested angle between generating
+   * radii (where angle can be given explicitly with
+   * LW_LINEARIZE_TOLERANCE_TYPE_MAX_ANGLE or implicitly
+   * with LW_LINEARIZE_TOLERANCE_TYPE_SEGS_PER_QUAD or
+   * LW_LINEARIZE_TOLERANCE_TYPE_MAX_DEVIATION).
+   *
+   * It only makes sense with LW_LINEARIZE_FLAG_SYMMETRIC
+   * which would otherwise reduce the angle as needed to
+   * keep it constant among all radiis so that all
+   * segments are of the same length.
+   *
+   * When this flag is set, the first and last generating
+   * angles (and thus the first and last segments) may
+   * instead be smaller (shorter) than the others.
+   *
+   */
+	LW_LINEARIZE_FLAG_RETAIN_ANGLE = 1 << 1
+} LW_LINEARIZE_FLAGS;
+
+/**
+ * @param geom input geometry
+ * @param tol tolerance, semantic driven by tolerance_type
+ * @param tolerance_type see LW_LINEARIZE_TOLERANCE_TYPE
+ * @param flags bitwise OR of operational flags, see LW_LINEARIZE_FLAGS
+ *
+ * @return a newly allocated LWGEOM
+ */
+extern LWGEOM* lwcurve_linearize(const LWGEOM *geom, double tol, LW_LINEARIZE_TOLERANCE_TYPE type, int flags);
+
 /*******************************************************************************
  * GEOS proxy functions on LWGEOM
  ******************************************************************************/

Modified: trunk/liblwgeom/liblwgeom_internal.h
===================================================================
--- trunk/liblwgeom/liblwgeom_internal.h	2017-06-13 13:29:29 UTC (rev 15429)
+++ trunk/liblwgeom/liblwgeom_internal.h	2017-06-19 16:06:59 UTC (rev 15430)
@@ -305,7 +305,6 @@
 /*
 * Segmentization
 */
-LWLINE *lwcircstring_stroke(const LWCIRCSTRING *icurve, uint32_t perQuad);
 LWLINE *lwcompound_stroke(const LWCOMPOUND *icompound, uint32_t perQuad);
 LWPOLY *lwcurvepoly_stroke(const LWCURVEPOLY *curvepoly, uint32_t perQuad);
 

Modified: trunk/liblwgeom/lwstroke.c
===================================================================
--- trunk/liblwgeom/lwstroke.c	2017-06-13 13:29:29 UTC (rev 15429)
+++ trunk/liblwgeom/lwstroke.c	2017-06-19 16:06:59 UTC (rev 15430)
@@ -19,6 +19,7 @@
  **********************************************************************
  *
  * Copyright (C) 2001-2006 Refractions Research Inc.
+ * Copyright (C) 2017      Sandro Santilli <strk at kbt.io>
  *
  **********************************************************************/
 
@@ -35,10 +36,6 @@
 #include "lwgeom_log.h"
 
 
-LWMLINE* lwmcurve_stroke(const LWMCURVE *mcurve, uint32_t perQuad);
-LWMPOLY* lwmsurface_stroke(const LWMSURFACE *msurface, uint32_t perQuad);
-LWCOLLECTION* lwcollection_stroke(const LWCOLLECTION *collection, uint32_t perQuad);
-
 LWGEOM* pta_unstroke(const POINTARRAY *points, int type, int srid);
 LWGEOM* lwline_unstroke(const LWLINE *line);
 LWGEOM* lwpolygon_unstroke(const LWPOLY *poly);
@@ -112,8 +109,25 @@
 	}
 }
 
-static POINTARRAY *
-lwcircle_stroke(const POINT4D *p1, const POINT4D *p2, const POINT4D *p3, uint32_t perQuad)
+/**
+ * Segmentize an arc
+ *
+ * @param to POINTARRAY to append segmentized vertices to
+ * @param p1 first point defining the arc
+ * @param p2 second point defining the arc
+ * @param p3 third point defining the arc
+ * @param tol tolerance, semantic driven by tolerance_type
+ * @param tolerance_type see LW_LINEARIZE_TOLERANCE_TYPE
+ * @param flags LW_LINEARIZE_FLAGS
+ *
+ * @return number of points appended (0 if collinear),
+ *         or -1 on error (lwerror would be called).
+ */
+static int
+lwarc_linearize(POINTARRAY *to,
+                 const POINT4D *p1, const POINT4D *p2, const POINT4D *p3,
+                 double tol, LW_LINEARIZE_TOLERANCE_TYPE tolerance_type,
+                 int flags)
 {
 	POINT2D center;
 	POINT2D *t1 = (POINT2D*)p1;
@@ -124,11 +138,13 @@
 	int clockwise = LW_TRUE;
 	double radius; /* Arc radius */
 	double increment; /* Angle per segment */
+	double angle_shift = 0;
 	double a1, a2, a3, angle;
-	POINTARRAY *pa;
+	POINTARRAY *pa = to;
 	int is_circle = LW_FALSE;
+	int points_added = 0;
 
-	LWDEBUG(2, "lwcircle_calculate_gbox called.");
+	LWDEBUG(2, "lwarc_linearize called.");
 
 	radius = lw_arc_center(t1, t2, t3, &center);
 	p2_side = lw_segment_side(t1, t3, t2);
@@ -139,7 +155,7 @@
 
 	/* Negative radius signals straight line, p1/p2/p3 are colinear */
 	if ( (radius < 0.0 || p2_side == 0) && ! is_circle )
-	    return NULL;
+	    return 0;
 
 	/* The side of the p1/p3 line that p2 falls on dictates the sweep
 	   direction from p1 to p3. */
@@ -148,17 +164,94 @@
 	else
 		clockwise = LW_FALSE;
 
-	increment = fabs(M_PI_2 / perQuad);
+	if ( tolerance_type == LW_LINEARIZE_TOLERANCE_TYPE_SEGS_PER_QUAD )
+	{{
+		int perQuad = rint(tol);
+		// error out if tol != perQuad ? (not-round)
+		if ( perQuad != tol )
+		{
+			lwerror("lwarc_linearize: segments per quadrant must be an integer value, got %.15g", tol, perQuad);
+			return -1;
+		}
+		if ( perQuad < 1 )
+		{
+			lwerror("lwarc_linearize: segments per quadrant must be at least 1, got %d", perQuad);
+			return -1;
+		}
+		increment = fabs(M_PI_2 / perQuad);
+		LWDEBUGF(2, "lwarc_linearize: perQuad:%d, increment:%g", perQuad, increment);
 
+	}}
+	else if ( tolerance_type == LW_LINEARIZE_TOLERANCE_TYPE_MAX_DEVIATION )
+	{{
+		double halfAngle;
+		if ( tol <= 0 )
+		{
+			lwerror("lwarc_linearize: max deviation must be bigger than 0, got %.15g", tol);
+			return -1;
+		}
+		halfAngle = acos( -tol / radius + 1 );
+		increment = 2 * halfAngle;
+		LWDEBUGF(2, "lwarc_linearize: maxDiff:%g, radius:%g, halfAngle:%g, increment:%g", tol, radius, halfAngle, increment);
+	}}
+	else if ( tolerance_type == LW_LINEARIZE_TOLERANCE_TYPE_MAX_ANGLE )
+	{
+		increment = tol;
+		if ( increment <= 0 )
+		{
+			lwerror("lwarc_linearize: max angle must be bigger than 0, got %.15g", tol);
+			return -1;
+		}
+	}
+	else
+	{
+		lwerror("lwarc_linearize: unsupported tolerance type %d", tolerance_type);
+		return LW_FALSE;
+	}
+
 	/* Angles of each point that defines the arc section */
 	a1 = atan2(p1->y - center.y, p1->x - center.x);
 	a2 = atan2(p2->y - center.y, p2->x - center.x);
 	a3 = atan2(p3->y - center.y, p3->x - center.x);
 
+	if ( flags & LW_LINEARIZE_FLAG_SYMMETRIC )
+	{
+		if ( flags & LW_LINEARIZE_FLAG_RETAIN_ANGLE )
+		{{
+			/* Total arc angle, in radians */
+			double angle = a3 - a1;
+			/* Number of steps */
+			int steps = floor(angle / increment);
+			/* Angle reminder */
+			double angle_reminder = angle - ( increment * steps );
+			angle_shift = angle_reminder / 2.0;
+
+			LWDEBUGF(2, "lwarc_linearize SYMMETRIC/RETAIN_ANGLE operation requested - "
+			         "A:%g - LINESTRING(%g %g,%g %g,%g %g) - R:%g",
+			         angle, p1->x, p1->y, center.x, center.y, p3->x, p3->y,
+			         angle_reminder);
+		}}
+		else
+		{{
+			/* Total arc angle, in radians */
+			double angle = fabs(a3 - a1);
+			/* Number of segments in output */
+			int segs = ceil(angle / increment);
+			/* Tweak increment to be regular for all the arc */
+			increment = angle/segs;
+
+			LWDEBUGF(2, "lwarc_linearize SYMMETRIC operation requested - "
+							"A:%g - LINESTRING(%g %g,%g %g,%g %g) - S:%d - I:%g",
+							angle, p1->x, p1->y, center.x, center.y, p3->x, p3->y,
+							segs, increment);
+		}}
+	}
+
 	/* p2 on left side => clockwise sweep */
 	if ( clockwise )
 	{
 		increment *= -1;
+		angle_shift *= -1;
 		/* Adjust a3 down so we can decrement from a1 to a3 cleanly */
 		if ( a3 > a1 )
 			a3 -= 2.0 * M_PI;
@@ -184,58 +277,61 @@
 		clockwise = LW_FALSE;
 	}
 
-	/* Initialize point array */
-	pa = ptarray_construct_empty(1, 1, 32);
-
 	/* Sweep from a1 to a3 */
 	ptarray_append_point(pa, p1, LW_FALSE);
-	for ( angle = a1 + increment; clockwise ? angle > a3 : angle < a3; angle += increment )
+	++points_added;
+	for ( angle = a1 + increment - angle_shift; clockwise ? angle > a3 : angle < a3; angle += increment )
 	{
 		pt.x = center.x + radius * cos(angle);
 		pt.y = center.y + radius * sin(angle);
 		pt.z = interpolate_arc(angle, a1, a2, a3, p1->z, p2->z, p3->z);
 		pt.m = interpolate_arc(angle, a1, a2, a3, p1->m, p2->m, p3->m);
 		ptarray_append_point(pa, &pt, LW_FALSE);
+		++points_added;
+		angle_shift = 0;
 	}
-	return pa;
+	return points_added;
 }
 
-LWLINE *
-lwcircstring_stroke(const LWCIRCSTRING *icurve, uint32_t perQuad)
+/*
+ * @param icurve input curve
+ * @param tol tolerance, semantic driven by tolerance_type
+ * @param tolerance_type see LW_LINEARIZE_TOLERANCE_TYPE
+ * @param flags see flags in lwarc_linearize
+ *
+ * @return a newly allocated LWLINE
+ */
+static LWLINE *
+lwcircstring_linearize(const LWCIRCSTRING *icurve, double tol,
+                        LW_LINEARIZE_TOLERANCE_TYPE tolerance_type,
+                        int flags)
 {
 	LWLINE *oline;
 	POINTARRAY *ptarray;
-	POINTARRAY *tmp;
 	uint32_t i, j;
 	POINT4D p1, p2, p3, p4;
+	int ret;
 
-	LWDEBUGF(2, "lwcircstring_stroke called., dim = %d", icurve->points->flags);
+	LWDEBUGF(2, "lwcircstring_linearize called., dim = %d", icurve->points->flags);
 
 	ptarray = ptarray_construct_empty(FLAGS_GET_Z(icurve->points->flags), FLAGS_GET_M(icurve->points->flags), 64);
 
 	for (i = 2; i < icurve->points->npoints; i+=2)
 	{
-		LWDEBUGF(3, "lwcircstring_stroke: arc ending at point %d", i);
+		LWDEBUGF(3, "lwcircstring_linearize: arc ending at point %d", i);
 
 		getPoint4d_p(icurve->points, i - 2, &p1);
 		getPoint4d_p(icurve->points, i - 1, &p2);
 		getPoint4d_p(icurve->points, i, &p3);
-		tmp = lwcircle_stroke(&p1, &p2, &p3, perQuad);
 
-		if (tmp)
+		ret = lwarc_linearize(ptarray, &p1, &p2, &p3, tol, tolerance_type, flags);
+		if ( ret > 0 )
 		{
-			LWDEBUGF(3, "lwcircstring_stroke: generated %d points", tmp->npoints);
-
-			for (j = 0; j < tmp->npoints; j++)
-			{
-				getPoint4d_p(tmp, j, &p4);
-				ptarray_append_point(ptarray, &p4, LW_TRUE);
-			}
-			ptarray_free(tmp);
+			LWDEBUGF(3, "lwcircstring_linearize: generated %d points", tmp->npoints);
 		}
-		else
+		else if ( ret == 0 )
 		{
-			LWDEBUG(3, "lwcircstring_stroke: points are colinear, returning curve points as line");
+			LWDEBUG(3, "lwcircstring_linearize: points are colinear, returning curve points as line");
 
 			for (j = i - 2 ; j < i ; j++)
 			{
@@ -243,7 +339,12 @@
 				ptarray_append_point(ptarray, &p4, LW_TRUE);
 			}
 		}
-
+		else
+		{
+			/* An error occurred, lwerror should have been called by now */
+			ptarray_free(ptarray);
+			return NULL;
+		}
 	}
 	getPoint4d_p(icurve->points, icurve->points->npoints-1, &p1);
 	ptarray_append_point(ptarray, &p1, LW_TRUE);
@@ -252,8 +353,18 @@
 	return oline;
 }
 
-LWLINE *
-lwcompound_stroke(const LWCOMPOUND *icompound, uint32_t perQuad)
+/*
+ * @param icompound input compound curve
+ * @param tol tolerance, semantic driven by tolerance_type
+ * @param tolerance_type see LW_LINEARIZE_TOLERANCE_TYPE
+ * @param flags see flags in lwarc_linearize
+ *
+ * @return a newly allocated LWLINE
+ */
+static LWLINE *
+lwcompound_linearize(const LWCOMPOUND *icompound, double tol,
+                      LW_LINEARIZE_TOLERANCE_TYPE tolerance_type,
+                      int flags)
 {
 	LWGEOM *geom;
 	POINTARRAY *ptarray = NULL, *ptarray_out = NULL;
@@ -270,7 +381,7 @@
 		geom = icompound->geoms[i];
 		if (geom->type == CIRCSTRINGTYPE)
 		{
-			tmp = lwcircstring_stroke((LWCIRCSTRING *)geom, perQuad);
+			tmp = lwcircstring_linearize((LWCIRCSTRING *)geom, tol, tolerance_type, flags);
 			for (j = 0; j < tmp->points->npoints; j++)
 			{
 				getPoint4d_p(tmp->points, j, &p);
@@ -299,16 +410,34 @@
 	return lwline_construct(icompound->srid, NULL, ptarray_out);
 }
 
-LWPOLY *
-lwcurvepoly_stroke(const LWCURVEPOLY *curvepoly, uint32_t perQuad)
+/* Kept for backward compatibility - TODO: drop */
+LWLINE *
+lwcompound_stroke(const LWCOMPOUND *icompound, uint32_t perQuad)
 {
+		return lwcompound_linearize(icompound, perQuad, LW_LINEARIZE_TOLERANCE_TYPE_SEGS_PER_QUAD, 0);
+}
+
+
+/*
+ * @param icompound input curve polygon
+ * @param tol tolerance, semantic driven by tolerance_type
+ * @param tolerance_type see LW_LINEARIZE_TOLERANCE_TYPE
+ * @param flags see flags in lwarc_linearize
+ *
+ * @return a newly allocated LWPOLY
+ */
+static LWPOLY *
+lwcurvepoly_linearize(const LWCURVEPOLY *curvepoly, double tol,
+                       LW_LINEARIZE_TOLERANCE_TYPE tolerance_type,
+                       int flags)
+{
 	LWPOLY *ogeom;
 	LWGEOM *tmp;
 	LWLINE *line;
 	POINTARRAY **ptarray;
 	int i;
 
-	LWDEBUG(2, "lwcurvepoly_stroke called.");
+	LWDEBUG(2, "lwcurvepoly_linearize called.");
 
 	ptarray = lwalloc(sizeof(POINTARRAY *)*curvepoly->nrings);
 
@@ -317,7 +446,7 @@
 		tmp = curvepoly->rings[i];
 		if (tmp->type == CIRCSTRINGTYPE)
 		{
-			line = lwcircstring_stroke((LWCIRCSTRING *)tmp, perQuad);
+			line = lwcircstring_linearize((LWCIRCSTRING *)tmp, tol, tolerance_type, flags);
 			ptarray[i] = ptarray_clone_deep(line->points);
 			lwline_free(line);
 		}
@@ -328,7 +457,7 @@
 		}
 		else if (tmp->type == COMPOUNDTYPE)
 		{
-			line = lwcompound_stroke((LWCOMPOUND *)tmp, perQuad);
+			line = lwcompound_linearize((LWCOMPOUND *)tmp, tol, tolerance_type, flags);
 			ptarray[i] = ptarray_clone_deep(line->points);
 			lwline_free(line);
 		}
@@ -343,14 +472,32 @@
 	return ogeom;
 }
 
-LWMLINE *
-lwmcurve_stroke(const LWMCURVE *mcurve, uint32_t perQuad)
+/* Kept for backward compatibility - TODO: drop */
+LWPOLY *
+lwcurvepoly_stroke(const LWCURVEPOLY *curvepoly, uint32_t perQuad)
 {
+		return lwcurvepoly_linearize(curvepoly, perQuad, LW_LINEARIZE_TOLERANCE_TYPE_SEGS_PER_QUAD, 0);
+}
+
+
+/**
+ * @param mcurve input compound curve
+ * @param tol tolerance, semantic driven by tolerance_type
+ * @param tolerance_type see LW_LINEARIZE_TOLERANCE_TYPE
+ * @param flags see flags in lwarc_linearize
+ *
+ * @return a newly allocated LWMLINE
+ */
+static LWMLINE *
+lwmcurve_linearize(const LWMCURVE *mcurve, double tol,
+                    LW_LINEARIZE_TOLERANCE_TYPE type,
+                    int flags)
+{
 	LWMLINE *ogeom;
 	LWGEOM **lines;
 	int i;
 
-	LWDEBUGF(2, "lwmcurve_stroke called, geoms=%d, dim=%d.", mcurve->ngeoms, FLAGS_NDIMS(mcurve->flags));
+	LWDEBUGF(2, "lwmcurve_linearize called, geoms=%d, dim=%d.", mcurve->ngeoms, FLAGS_NDIMS(mcurve->flags));
 
 	lines = lwalloc(sizeof(LWGEOM *)*mcurve->ngeoms);
 
@@ -359,7 +506,7 @@
 		const LWGEOM *tmp = mcurve->geoms[i];
 		if (tmp->type == CIRCSTRINGTYPE)
 		{
-			lines[i] = (LWGEOM *)lwcircstring_stroke((LWCIRCSTRING *)tmp, perQuad);
+			lines[i] = (LWGEOM *)lwcircstring_linearize((LWCIRCSTRING *)tmp, tol, type, flags);
 		}
 		else if (tmp->type == LINETYPE)
 		{
@@ -367,7 +514,7 @@
 		}
 		else if (tmp->type == COMPOUNDTYPE)
 		{
-			lines[i] = (LWGEOM *)lwcompound_stroke((LWCOMPOUND *)tmp, perQuad);
+			lines[i] = (LWGEOM *)lwcompound_linearize((LWCOMPOUND *)tmp, tol, type, flags);
 		}
 		else
 		{
@@ -380,8 +527,18 @@
 	return ogeom;
 }
 
-LWMPOLY *
-lwmsurface_stroke(const LWMSURFACE *msurface, uint32_t perQuad)
+/**
+ * @param msurface input multi surface
+ * @param tol tolerance, semantic driven by tolerance_type
+ * @param tolerance_type see LW_LINEARIZE_TOLERANCE_TYPE
+ * @param flags see flags in lwarc_linearize
+ *
+ * @return a newly allocated LWMPOLY
+ */
+static LWMPOLY *
+lwmsurface_linearize(const LWMSURFACE *msurface, double tol,
+                      LW_LINEARIZE_TOLERANCE_TYPE type,
+                      int flags)
 {
 	LWMPOLY *ogeom;
 	LWGEOM *tmp;
@@ -390,7 +547,7 @@
 	POINTARRAY **ptarray;
 	int i, j;
 
-	LWDEBUG(2, "lwmsurface_stroke called.");
+	LWDEBUG(2, "lwmsurface_linearize called.");
 
 	polys = lwalloc(sizeof(LWGEOM *)*msurface->ngeoms);
 
@@ -399,7 +556,7 @@
 		tmp = msurface->geoms[i];
 		if (tmp->type == CURVEPOLYTYPE)
 		{
-			polys[i] = (LWGEOM *)lwcurvepoly_stroke((LWCURVEPOLY *)tmp, perQuad);
+			polys[i] = (LWGEOM *)lwcurvepoly_linearize((LWCURVEPOLY *)tmp, tol, type, flags);
 		}
 		else if (tmp->type == POLYGONTYPE)
 		{
@@ -416,15 +573,25 @@
 	return ogeom;
 }
 
-LWCOLLECTION *
-lwcollection_stroke(const LWCOLLECTION *collection, uint32_t perQuad)
+/**
+ * @param collection input geometry collection
+ * @param tol tolerance, semantic driven by tolerance_type
+ * @param tolerance_type see LW_LINEARIZE_TOLERANCE_TYPE
+ * @param flags see flags in lwarc_linearize
+ *
+ * @return a newly allocated LWCOLLECTION
+ */
+static LWCOLLECTION *
+lwcollection_linearize(const LWCOLLECTION *collection, double tol,
+                    LW_LINEARIZE_TOLERANCE_TYPE type,
+                    int flags)
 {
 	LWCOLLECTION *ocol;
 	LWGEOM *tmp;
 	LWGEOM **geoms;
 	int i;
 
-	LWDEBUG(2, "lwcollection_stroke called.");
+	LWDEBUG(2, "lwcollection_linearize called.");
 
 	geoms = lwalloc(sizeof(LWGEOM *)*collection->ngeoms);
 
@@ -434,18 +601,18 @@
 		switch (tmp->type)
 		{
 		case CIRCSTRINGTYPE:
-			geoms[i] = (LWGEOM *)lwcircstring_stroke((LWCIRCSTRING *)tmp, perQuad);
+			geoms[i] = (LWGEOM *)lwcircstring_linearize((LWCIRCSTRING *)tmp, tol, type, flags);
 			break;
 		case COMPOUNDTYPE:
-			geoms[i] = (LWGEOM *)lwcompound_stroke((LWCOMPOUND *)tmp, perQuad);
+			geoms[i] = (LWGEOM *)lwcompound_linearize((LWCOMPOUND *)tmp, tol, type, flags);
 			break;
 		case CURVEPOLYTYPE:
-			geoms[i] = (LWGEOM *)lwcurvepoly_stroke((LWCURVEPOLY *)tmp, perQuad);
+			geoms[i] = (LWGEOM *)lwcurvepoly_linearize((LWCURVEPOLY *)tmp, tol, type, flags);
 			break;
 		case MULTICURVETYPE:
 		case MULTISURFACETYPE:
 		case COLLECTIONTYPE:
-			geoms[i] = (LWGEOM *)lwcollection_stroke((LWCOLLECTION *)tmp, perQuad);
+			geoms[i] = (LWGEOM *)lwcollection_linearize((LWCOLLECTION *)tmp, tol, type, flags);
 			break;
 		default:
 			geoms[i] = lwgeom_clone(tmp);
@@ -457,28 +624,30 @@
 }
 
 LWGEOM *
-lwgeom_stroke(const LWGEOM *geom, uint32_t perQuad)
+lwcurve_linearize(const LWGEOM *geom, double tol,
+                  LW_LINEARIZE_TOLERANCE_TYPE type,
+                  int flags)
 {
 	LWGEOM * ogeom = NULL;
 	switch (geom->type)
 	{
 	case CIRCSTRINGTYPE:
-		ogeom = (LWGEOM *)lwcircstring_stroke((LWCIRCSTRING *)geom, perQuad);
+		ogeom = (LWGEOM *)lwcircstring_linearize((LWCIRCSTRING *)geom, tol, type, flags);
 		break;
 	case COMPOUNDTYPE:
-		ogeom = (LWGEOM *)lwcompound_stroke((LWCOMPOUND *)geom, perQuad);
+		ogeom = (LWGEOM *)lwcompound_linearize((LWCOMPOUND *)geom, tol, type, flags);
 		break;
 	case CURVEPOLYTYPE:
-		ogeom = (LWGEOM *)lwcurvepoly_stroke((LWCURVEPOLY *)geom, perQuad);
+		ogeom = (LWGEOM *)lwcurvepoly_linearize((LWCURVEPOLY *)geom, tol, type, flags);
 		break;
 	case MULTICURVETYPE:
-		ogeom = (LWGEOM *)lwmcurve_stroke((LWMCURVE *)geom, perQuad);
+		ogeom = (LWGEOM *)lwmcurve_linearize((LWMCURVE *)geom, tol, type, flags);
 		break;
 	case MULTISURFACETYPE:
-		ogeom = (LWGEOM *)lwmsurface_stroke((LWMSURFACE *)geom, perQuad);
+		ogeom = (LWGEOM *)lwmsurface_linearize((LWMSURFACE *)geom, tol, type, flags);
 		break;
 	case COLLECTIONTYPE:
-		ogeom = (LWGEOM *)lwcollection_stroke((LWCOLLECTION *)geom, perQuad);
+		ogeom = (LWGEOM *)lwcollection_linearize((LWCOLLECTION *)geom, tol, type, flags);
 		break;
 	default:
 		ogeom = lwgeom_clone(geom);
@@ -486,6 +655,13 @@
 	return ogeom;
 }
 
+/* Kept for backward compatibility - TODO: drop */
+LWGEOM *
+lwgeom_stroke(const LWGEOM *geom, uint32_t perQuad)
+{
+	return lwcurve_linearize(geom, perQuad, LW_LINEARIZE_TOLERANCE_TYPE_SEGS_PER_QUAD, 0);
+}
+
 /**
  * Return ABC angle in radians
  * TODO: move to lwalgorithm

Modified: trunk/postgis/lwgeom_sqlmm.c
===================================================================
--- trunk/postgis/lwgeom_sqlmm.c	2017-06-13 13:29:29 UTC (rev 15429)
+++ trunk/postgis/lwgeom_sqlmm.c	2017-06-19 16:06:59 UTC (rev 15430)
@@ -62,6 +62,8 @@
  * Curve centers are determined by projecting the defining points into the 2d
  * plane.  Z and M values are assigned by linear interpolation between
  * defining points.
+ *
+ * TODO: drop, use ST_CurveToLine instead
  */
 PG_FUNCTION_INFO_V1(LWGEOM_curve_segmentize);
 Datum LWGEOM_curve_segmentize(PG_FUNCTION_ARGS)
@@ -84,16 +86,43 @@
 	igeom = lwgeom_from_gserialized(geom);
 	ogeom = lwgeom_stroke(igeom, perQuad);
 	lwgeom_free(igeom);
-	
+
 	if (ogeom == NULL)
 		PG_RETURN_NULL();
-		
+
 	ret = geometry_serialize(ogeom);
 	lwgeom_free(ogeom);
 	PG_FREE_IF_COPY(geom, 0);
 	PG_RETURN_POINTER(ret);
 }
 
+PG_FUNCTION_INFO_V1(ST_CurveToLine);
+Datum ST_CurveToLine(PG_FUNCTION_ARGS)
+{
+	GSERIALIZED *geom = PG_GETARG_GSERIALIZED_P(0);
+	double tol = PG_GETARG_FLOAT8(1);
+	int toltype = PG_GETARG_INT32(2);
+	int flags = PG_GETARG_INT32(3);
+	GSERIALIZED *ret;
+	LWGEOM *igeom = NULL, *ogeom = NULL;
+
+	POSTGIS_DEBUG(2, "ST_CurveToLine called.");
+
+	POSTGIS_DEBUGF(3, "tol = %g, typ = %d, flg = %d", tol, toltype, flags);
+
+	igeom = lwgeom_from_gserialized(geom);
+	ogeom = lwcurve_linearize(igeom, tol, toltype, flags);
+	lwgeom_free(igeom);
+
+	if (ogeom == NULL)
+		PG_RETURN_NULL();
+
+	ret = geometry_serialize(ogeom);
+	lwgeom_free(ogeom);
+	PG_FREE_IF_COPY(geom, 0);
+	PG_RETURN_POINTER(ret);
+}
+
 PG_FUNCTION_INFO_V1(LWGEOM_line_desegmentize);
 Datum LWGEOM_line_desegmentize(PG_FUNCTION_ARGS)
 {

Modified: trunk/postgis/postgis.sql.in
===================================================================
--- trunk/postgis/postgis.sql.in	2017-06-13 13:29:29 UTC (rev 15429)
+++ trunk/postgis/postgis.sql.in	2017-06-19 16:06:59 UTC (rev 15430)
@@ -5572,15 +5572,38 @@
 --
 -- SQL-MM
 --
+-- ST_CurveToLine(Geometry geometry, Tolerance float8, ToleranceType integer, Flags integer)
+--
+-- Converts a given geometry to a linear geometry.  Each curveed
+-- geometry or segment is converted into a linear approximation using
+-- the given tolerance.
+--
+-- Semantic of tolerance depends on the `toltype` argument, which can be:
+--    0: Tolerance is number of segments per quadrant
+--    1: Tolerance is max distance between curve and line
+--    2: Tolerance is max angle between radii defining line vertices
+--
+-- Supported flags:
+--    1: Symmetric output (result in same vertices when inverting the curve)
+--
+-- Availability: 2.4.0
+--
+CREATE OR REPLACE FUNCTION ST_CurveToLine(geom geometry, tol float8, toltype integer, flags integer)
+	RETURNS geometry
+	AS 'MODULE_PATHNAME', 'ST_CurveToLine'
+	LANGUAGE 'c' IMMUTABLE STRICT _PARALLEL;
+--
+-- SQL-MM
+--
 -- ST_CurveToLine(Geometry geometry, SegmentsPerQuarter integer)
 --
 -- Converts a given geometry to a linear geometry.  Each curveed
 -- geometry or segment is converted into a linear approximation using
 -- the given number of segments per quarter circle.
+--
 CREATE OR REPLACE FUNCTION ST_CurveToLine(geometry, integer)
-	RETURNS geometry
-	AS 'MODULE_PATHNAME', 'LWGEOM_curve_segmentize'
-	LANGUAGE 'c' IMMUTABLE STRICT _PARALLEL;
+	RETURNS geometry AS 'SELECT ST_CurveToLine($1, $2::float8, 0, 0)'
+	LANGUAGE 'sql' IMMUTABLE STRICT _PARALLEL;
 --
 -- SQL-MM
 --
@@ -5590,7 +5613,7 @@
 -- geometry or segment is converted into a linear approximation using
 -- the default value of 32 segments per quarter circle
 CREATE OR REPLACE FUNCTION ST_CurveToLine(geometry)
-	RETURNS geometry AS 'SELECT ST_CurveToLine($1, 32)'
+	RETURNS geometry AS 'SELECT ST_CurveToLine($1, 32::integer)'
 	LANGUAGE 'sql' IMMUTABLE STRICT _PARALLEL;
 
 CREATE OR REPLACE FUNCTION ST_HasArc(Geometry geometry)

Modified: trunk/regress/Makefile.in
===================================================================
--- trunk/regress/Makefile.in	2017-06-13 13:29:29 UTC (rev 15429)
+++ trunk/regress/Makefile.in	2017-06-19 16:06:59 UTC (rev 15430)
@@ -83,6 +83,7 @@
 	cluster \
 	concave_hull\
 	ctors \
+	curvetoline \
 	dump \
 	dumppoints \
 	empty \

Added: trunk/regress/curvetoline.sql
===================================================================
--- trunk/regress/curvetoline.sql	                        (rev 0)
+++ trunk/regress/curvetoline.sql	2017-06-19 16:06:59 UTC (rev 15430)
@@ -0,0 +1,52 @@
+
+
+-- Semantic of tolerance depends on the `toltype` argument, which can be:
+--    0: Tolerance is number of segments per quadrant
+--    1: Tolerance is max distance between curve and line
+--    2: Tolerance is max angle between radii defining line vertices
+--
+-- Supported flags:
+--    1: Symmetric output (result in same vertices when inverting the curve)
+
+SELECT 'semicircle1', ST_AsText(ST_SnapToGrid(ST_CurveToLine(
+ 'CIRCULARSTRING(0 0,100 -100,200 0)'::geometry,
+	3, -- Tolerance
+	0, -- Above is number of segments per quadrant
+	0  -- no flags
+), 2));
+
+SELECT 'semicircle2', ST_AsText(ST_SnapToGrid(ST_CurveToLine(
+ 'CIRCULARSTRING(0 0,100 -100,200 0)'::geometry,
+	20, -- Tolerance
+	1, -- Above is max distance between curve and line
+	0  -- no flags
+), 2));
+
+SELECT 'semicircle2.sym', ST_AsText(ST_SnapToGrid(ST_CurveToLine(
+ 'CIRCULARSTRING(0 0,100 -100,200 0)'::geometry,
+	20, -- Tolerance
+	1, -- Above is max distance between curve and line
+	1  -- Symmetric flag
+), 2));
+
+SELECT 'semicircle3', ST_AsText(ST_SnapToGrid(ST_CurveToLine(
+ 'CIRCULARSTRING(0 0,100 -100,200 0)'::geometry,
+	radians(40), -- Tolerance
+	2, -- Above is max angle between generating radii
+	0  -- no flags
+), 2));
+
+SELECT 'semicircle3.sym', ST_AsText(ST_SnapToGrid(ST_CurveToLine(
+ 'CIRCULARSTRING(0 0,100 -100,200 0)'::geometry,
+	radians(40), -- Tolerance
+	2, -- Above is max angle between generating radii
+	1  -- Symmetric flag
+), 2));
+SELECT 'semicircle3.sym.ret', ST_AsText(ST_SnapToGrid(ST_CurveToLine(
+ 'CIRCULARSTRING(0 0,100 -100,200 0)'::geometry,
+	radians(40), -- Tolerance
+	2, -- Above is max angle between generating radii
+	3  -- Symmetric and RetainAngle flags
+), 2));
+
+

Added: trunk/regress/curvetoline_expected
===================================================================
--- trunk/regress/curvetoline_expected	                        (rev 0)
+++ trunk/regress/curvetoline_expected	2017-06-19 16:06:59 UTC (rev 15430)
@@ -0,0 +1,6 @@
+semicircle1|LINESTRING(0 0,14 -50,50 -86,100 -100,150 -86,186 -50,200 0)
+semicircle2|LINESTRING(0 0,72 -96,184 -54,200 0)
+semicircle2.sym|LINESTRING(0 0,50 -86,150 -86,200 0)
+semicircle3|LINESTRING(0 0,24 -64,82 -98,150 -86,194 -34,200 0)
+semicircle3.sym|LINESTRING(0 0,20 -58,70 -96,130 -96,180 -58,200 0)
+semicircle3.sym.ret|LINESTRING(0 0,14 -50,66 -94,134 -94,186 -50,200 0)

Modified: trunk/regress/sql-mm-circularstring.sql
===================================================================
--- trunk/regress/sql-mm-circularstring.sql	2017-06-13 13:29:29 UTC (rev 15429)
+++ trunk/regress/sql-mm-circularstring.sql	2017-06-19 16:06:59 UTC (rev 15430)
@@ -233,4 +233,3 @@
 
 -- See http://trac.osgeo.org/postgis/ticket/2410
 SELECT 'straight_curve', ST_AsText(ST_CurveToLine(ST_GeomFromEWKT('CIRCULARSTRING(0 0,1 0,2 0,3 0,4 0)')));
-



More information about the postgis-tickets mailing list