]> granicus.if.org Git - postgis/commitdiff
Implement extended ST_CurveToLine signature
authorSandro Santilli <strk@kbt.io>
Mon, 19 Jun 2017 16:06:59 +0000 (16:06 +0000)
committerSandro Santilli <strk@kbt.io>
Mon, 19 Jun 2017 16:06:59 +0000 (16:06 +0000)
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

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

13 files changed:
doc/reference_processing.xml
liblwgeom/cunit/Makefile.in
liblwgeom/cunit/cu_lwstroke.c [new file with mode: 0644]
liblwgeom/cunit/cu_tester.c
liblwgeom/liblwgeom.h.in
liblwgeom/liblwgeom_internal.h
liblwgeom/lwstroke.c
postgis/lwgeom_sqlmm.c
postgis/postgis.sql.in
regress/Makefile.in
regress/curvetoline.sql [new file with mode: 0644]
regress/curvetoline_expected [new file with mode: 0644]
regress/sql-mm-circularstring.sql

index 691b85e5a3ad5c27fa5048c8094a29c8e0a5b146..424d3c949adb215266756e5cbb5affaa22b07490 100644 (file)
@@ -845,6 +845,13 @@ POLYGON((50 5,10 8,10 10,100 190,150 30,150 10,50 5))
                        <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 @@ POLYGON((50 5,10 8,10 10,100 190,150 30,150 10,50 5))
                <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,6 +963,18 @@ st_astext
  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>
index e6cb4abe3530a4e6d46b0404de03c0a5652ab6c3..8ff18ad06a12b54536ac84376d3a332fee700076 100644 (file)
@@ -39,6 +39,7 @@ OBJS= \
        cu_node.o \
        cu_clip_by_rect.o \
        cu_libgeom.o \
+       cu_lwstroke.o \
        cu_split.o \
        cu_stringbuffer.o \
        cu_triangulate.o \
diff --git a/liblwgeom/cunit/cu_lwstroke.c b/liblwgeom/cunit/cu_lwstroke.c
new file mode 100644 (file)
index 0000000..48d9850
--- /dev/null
@@ -0,0 +1,253 @@
+/**********************************************************************
+ *
+ * PostGIS - Spatial Types for PostgreSQL
+ * http://postgis.net
+ *
+ * Copyright (C) 2017 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 <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);
+}
index 5a8b354a9b8ae62d52bd714d1bf05775f4ead0d1..04b8b5eef6bff19d0a297ddedd967cd788bf1037 100644 (file)
@@ -44,6 +44,7 @@ extern void in_geojson_suite_setup(void);
 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 @@ PG_SuiteSetup setupfuncs[] =
     iterator_suite_setup,
        twkb_in_suite_setup,
        libgeom_suite_setup,
+       lwstroke_suite_setup,
        measures_suite_setup,
        effectivearea_suite_setup,
        minimum_bounding_circle_suite_setup,
index 07fa591be9ae924f7075043c1da2984a58fbbe16..64fecece9d79b80ead4851e032acddac7724b670 100644 (file)
@@ -2158,13 +2158,80 @@ extern uint8_t* lwgeom_to_twkb(const LWGEOM *geom, uint8_t variant, int8_t preci
 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
  ******************************************************************************/
index c84cee3580c38cfc65054b65602912cc15c92a05..3f5edc1103223187756925937c46a8079eb95d3b 100644 (file)
@@ -305,7 +305,6 @@ double lwtriangle_perimeter_2d(const LWTRIANGLE *triangle);
 /*
 * 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);
 
index 71b9e1fddddeb7c420c4d3e469c2c750b723cf7f..e3cca86a77d57a43ae48de2d5f8e4391ef32e848 100644 (file)
@@ -19,6 +19,7 @@
  **********************************************************************
  *
  * Copyright (C) 2001-2006 Refractions Research Inc.
+ * Copyright (C) 2017      Sandro Santilli <strk@kbt.io>
  *
  **********************************************************************/
 
 #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 double interpolate_arc(double angle, double a1, double a2, double a3, dou
        }
 }
 
-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 @@ lwcircle_stroke(const POINT4D *p1, const POINT4D *p2, const POINT4D *p3, uint32_
        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 @@ lwcircle_stroke(const POINT4D *p1, const POINT4D *p2, const POINT4D *p3, uint32_
 
        /* 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 @@ lwcircle_stroke(const POINT4D *p1, const POINT4D *p2, const POINT4D *p3, uint32_
        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 @@ lwcircle_stroke(const POINT4D *p1, const POINT4D *p2, const POINT4D *p3, uint32_
                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 @@ lwcircstring_stroke(const LWCIRCSTRING *icurve, uint32_t perQuad)
                                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 @@ lwcircstring_stroke(const LWCIRCSTRING *icurve, uint32_t perQuad)
        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 @@ lwcompound_stroke(const LWCOMPOUND *icompound, uint32_t perQuad)
                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,8 +410,26 @@ lwcompound_stroke(const LWCOMPOUND *icompound, uint32_t perQuad)
        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;
@@ -308,7 +437,7 @@ lwcurvepoly_stroke(const LWCURVEPOLY *curvepoly, uint32_t perQuad)
        POINTARRAY **ptarray;
        int i;
 
-       LWDEBUG(2, "lwcurvepoly_stroke called.");
+       LWDEBUG(2, "lwcurvepoly_linearize called.");
 
        ptarray = lwalloc(sizeof(POINTARRAY *)*curvepoly->nrings);
 
@@ -317,7 +446,7 @@ lwcurvepoly_stroke(const LWCURVEPOLY *curvepoly, uint32_t perQuad)
                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 @@ lwcurvepoly_stroke(const LWCURVEPOLY *curvepoly, uint32_t perQuad)
                }
                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 @@ lwcurvepoly_stroke(const LWCURVEPOLY *curvepoly, uint32_t perQuad)
        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 @@ lwmcurve_stroke(const LWMCURVE *mcurve, uint32_t perQuad)
                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 @@ lwmcurve_stroke(const LWMCURVE *mcurve, uint32_t perQuad)
                }
                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 @@ lwmcurve_stroke(const LWMCURVE *mcurve, uint32_t perQuad)
        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 @@ lwmsurface_stroke(const LWMSURFACE *msurface, uint32_t perQuad)
        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 @@ lwmsurface_stroke(const LWMSURFACE *msurface, uint32_t perQuad)
                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 @@ lwmsurface_stroke(const LWMSURFACE *msurface, uint32_t perQuad)
        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 @@ lwcollection_stroke(const LWCOLLECTION *collection, uint32_t perQuad)
                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 @@ lwcollection_stroke(const LWCOLLECTION *collection, uint32_t perQuad)
 }
 
 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 @@ lwgeom_stroke(const LWGEOM *geom, uint32_t perQuad)
        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
index 16a96522a1e0ad97249eda3f30a95694b0e3351b..f90ed26840fa5cc8ce2ec2f4b23352b4b00f739b 100644 (file)
@@ -62,6 +62,8 @@ Datum LWGEOM_has_arc(PG_FUNCTION_ARGS)
  * 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,10 +86,37 @@ Datum LWGEOM_curve_segmentize(PG_FUNCTION_ARGS)
        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);
index 90a5900a50ea795e2b35584be36a490f4f7598a0..d62b87b22ffa1941f13a6f0c724f29d83dd1412f 100644 (file)
@@ -5572,15 +5572,38 @@ CREATE OR REPLACE FUNCTION ST_CoordDim(Geometry geometry)
 --
 -- 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 @@ CREATE OR REPLACE FUNCTION ST_CurveToLine(geometry, integer)
 -- 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)
index 1fb4fd6a1bfd11ccdf9413f8d88a2dde72dce736..143e2b052c29872f2346ec52f3a059184e55df82 100644 (file)
@@ -83,6 +83,7 @@ TESTS = \
        cluster \
        concave_hull\
        ctors \
+       curvetoline \
        dump \
        dumppoints \
        empty \
diff --git a/regress/curvetoline.sql b/regress/curvetoline.sql
new file mode 100644 (file)
index 0000000..110269a
--- /dev/null
@@ -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));
+
+
diff --git a/regress/curvetoline_expected b/regress/curvetoline_expected
new file mode 100644 (file)
index 0000000..9961272
--- /dev/null
@@ -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)
index 324648876a73e5167217500f51dea25991f80f30..75b9a0eaaa0d58bc350f2d01b22748ec0b33d928 100644 (file)
@@ -233,4 +233,3 @@ SELECT 'npoints_is_five', ST_NumPoints(ST_GeomFromEWKT('CIRCULARSTRING(0 0,2 0,
 
 -- 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)')));
-