From: Sandro Santilli Date: Mon, 19 Jun 2017 16:06:59 +0000 (+0000) Subject: Implement extended ST_CurveToLine signature X-Git-Tag: 2.4.0alpha~69 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=149773b6b01c69926ab26723699ce22ff67ca15c;p=postgis 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 git-svn-id: http://svn.osgeo.org/postgis/trunk@15430 b70326c6-7e19-0410-871a-916f4a2858ee --- diff --git a/doc/reference_processing.xml b/doc/reference_processing.xml index 691b85e5a..424d3c949 100644 --- a/doc/reference_processing.xml +++ b/doc/reference_processing.xml @@ -845,6 +845,13 @@ POLYGON((50 5,10 8,10 10,100 190,150 30,150 10,50 5)) geometry curveGeom integer segments_per_qtr_circle + + geometry ST_CurveToLine + geometry curveGeom + float tolerance + integer tolerance_type + integer flags + @@ -852,9 +859,44 @@ POLYGON((50 5,10 8,10 10,100 190,150 30,150 10,50 5)) Description Converst a CIRCULAR STRING to regular LINESTRING or CURVEPOLYGON to POLYGON. Useful for outputting to devices that can't support CIRCULARSTRING geometry types + 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 + 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). + + +The 'tolerance_type' argument determines interpretation of the +`tolerance` argument. It can take the following values: + + + 0 (default): Tolerance is max segments per quadrant. + + + 1: Tolerance is max-deviation of line from curve, in source units. + + + 2: Tolerance is max-angle, in radians, between generating radii. + + + + + +The 'flags' argument is a bitfield. 0 by default. +Supported bits are: + + + 1: Symmetric (orientation idependent) output. + + + 2: Retain angle, avoids reducing angles (segment lengths) when producing symmetric output. Has no effect when Symmetric flag is off. + + + + Availability: 1.2.2? + Changed: 2.4.0 added support for max-deviation and max-angle tolerance, and for symmetric output. + &sfs_compliant; &sqlmm_compliant; SQL-MM 3: 7.1.7 &Z_support; @@ -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) + diff --git a/liblwgeom/cunit/Makefile.in b/liblwgeom/cunit/Makefile.in index e6cb4abe3..8ff18ad06 100644 --- a/liblwgeom/cunit/Makefile.in +++ b/liblwgeom/cunit/Makefile.in @@ -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 index 000000000..48d9850f6 --- /dev/null +++ b/liblwgeom/cunit/cu_lwstroke.c @@ -0,0 +1,253 @@ +/********************************************************************** + * + * PostGIS - Spatial Types for PostgreSQL + * http://postgis.net + * + * Copyright (C) 2017 Sandro Santilli + * + * 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 +#include +#include +#include /* 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); +} diff --git a/liblwgeom/cunit/cu_tester.c b/liblwgeom/cunit/cu_tester.c index 5a8b354a9..04b8b5eef 100644 --- a/liblwgeom/cunit/cu_tester.c +++ b/liblwgeom/cunit/cu_tester.c @@ -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, diff --git a/liblwgeom/liblwgeom.h.in b/liblwgeom/liblwgeom.h.in index 07fa591be..64fecece9 100644 --- a/liblwgeom/liblwgeom.h.in +++ b/liblwgeom/liblwgeom.h.in @@ -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 ******************************************************************************/ diff --git a/liblwgeom/liblwgeom_internal.h b/liblwgeom/liblwgeom_internal.h index c84cee358..3f5edc110 100644 --- a/liblwgeom/liblwgeom_internal.h +++ b/liblwgeom/liblwgeom_internal.h @@ -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); diff --git a/liblwgeom/lwstroke.c b/liblwgeom/lwstroke.c index 71b9e1fdd..e3cca86a7 100644 --- a/liblwgeom/lwstroke.c +++ b/liblwgeom/lwstroke.c @@ -19,6 +19,7 @@ ********************************************************************** * * Copyright (C) 2001-2006 Refractions Research Inc. + * Copyright (C) 2017 Sandro Santilli * **********************************************************************/ @@ -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 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, ¢er); 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 diff --git a/postgis/lwgeom_sqlmm.c b/postgis/lwgeom_sqlmm.c index 16a96522a..f90ed2684 100644 --- a/postgis/lwgeom_sqlmm.c +++ b/postgis/lwgeom_sqlmm.c @@ -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); diff --git a/postgis/postgis.sql.in b/postgis/postgis.sql.in index 90a5900a5..d62b87b22 100644 --- a/postgis/postgis.sql.in +++ b/postgis/postgis.sql.in @@ -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) diff --git a/regress/Makefile.in b/regress/Makefile.in index 1fb4fd6a1..143e2b052 100644 --- a/regress/Makefile.in +++ b/regress/Makefile.in @@ -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 index 000000000..110269a93 --- /dev/null +++ b/regress/curvetoline.sql @@ -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 index 000000000..99612720a --- /dev/null +++ b/regress/curvetoline_expected @@ -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) diff --git a/regress/sql-mm-circularstring.sql b/regress/sql-mm-circularstring.sql index 324648876..75b9a0eaa 100644 --- a/regress/sql-mm-circularstring.sql +++ b/regress/sql-mm-circularstring.sql @@ -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)'))); -