PostGIS  2.4.9dev-r@@SVN_REVISION@@

◆ lwarc_linearize()

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 
)
static

Segmentize an arc.

Does not add the final vertex

Parameters
toPOINTARRAY to append segmentized vertices to
p1first point defining the arc
p2second point defining the arc
p3third point defining the arc
toltolerance, semantic driven by tolerance_type
tolerance_typesee LW_LINEARIZE_TOLERANCE_TYPE
flagsLW_LINEARIZE_FLAGS
Returns
number of points appended (0 if collinear), or -1 on error (lwerror would be called).

Definition at line 134 of file lwstroke.c.

References getPoint4d_p(), interpolate_arc(), lw_arc_center(), LW_FALSE, LW_LINEARIZE_FLAG_RETAIN_ANGLE, LW_LINEARIZE_FLAG_SYMMETRIC, LW_LINEARIZE_TOLERANCE_TYPE_MAX_ANGLE, LW_LINEARIZE_TOLERANCE_TYPE_MAX_DEVIATION, LW_LINEARIZE_TOLERANCE_TYPE_SEGS_PER_QUAD, lw_segment_side(), LW_TRUE, LWDEBUG, LWDEBUGF, lwerror(), POINT4D::m, POINTARRAY::npoints, ptarray_append_point(), ptarray_construct_empty(), ptarray_free(), ptarray_has_m(), ptarray_has_z(), ptarray_remove_point(), POINT2D::x, POINT4D::x, POINT2D::y, POINT4D::y, and POINT4D::z.

Referenced by lwcircstring_linearize().

138 {
139  POINT2D center;
140  POINT2D *t1 = (POINT2D*)p1;
141  POINT2D *t2 = (POINT2D*)p2;
142  POINT2D *t3 = (POINT2D*)p3;
143  POINT4D pt;
144  int p2_side = 0;
145  int clockwise = LW_TRUE;
146  double radius; /* Arc radius */
147  double increment; /* Angle per segment */
148  double angle_shift = 0;
149  double a1, a2, a3, angle;
150  POINTARRAY *pa = to;
151  int is_circle = LW_FALSE;
152  int points_added = 0;
153  int reverse = 0;
154 
155  LWDEBUG(2, "lwarc_linearize called.");
156 
157  LWDEBUGF(2, " curve is CIRCULARSTRING(%.15g %.15f, %.15f %.15f, %.15f %15f)",
158  t1->x, t1->y, t2->x, t2->y, t3->x, t3->y);
159 
160  p2_side = lw_segment_side(t1, t3, t2);
161 
162  LWDEBUGF(2, " p2 side is %d", p2_side);
163 
164  /* Force counterclockwise scan if SYMMETRIC operation is requsested */
165  if ( p2_side == -1 && flags & LW_LINEARIZE_FLAG_SYMMETRIC )
166  {
167  /* swap p1-p3 */
168  t1 = (POINT2D*)p3;
169  t3 = (POINT2D*)p1;
170  p1 = (POINT4D*)t1;
171  p3 = (POINT4D*)t3;
172  p2_side = 1;
173  reverse = 1;
174  }
175 
176  radius = lw_arc_center(t1, t2, t3, &center);
177  LWDEBUGF(2, " center is POINT(%.15g %.15g) - radius:%g", center.x, center.y, radius);
178 
179  /* Matched start/end points imply circle */
180  if ( p1->x == p3->x && p1->y == p3->y )
181  is_circle = LW_TRUE;
182 
183  /* Negative radius signals straight line, p1/p2/p3 are colinear */
184  if ( (radius < 0.0 || p2_side == 0) && ! is_circle )
185  return 0;
186 
187  /* The side of the p1/p3 line that p2 falls on dictates the sweep
188  direction from p1 to p3. */
189  if ( p2_side == -1 )
190  clockwise = LW_TRUE;
191  else
192  clockwise = LW_FALSE;
193 
194  if ( tolerance_type == LW_LINEARIZE_TOLERANCE_TYPE_SEGS_PER_QUAD )
195  {{
196  int perQuad = rint(tol);
197  // error out if tol != perQuad ? (not-round)
198  if ( perQuad != tol )
199  {
200  lwerror("lwarc_linearize: segments per quadrant must be an integer value, got %.15g", tol, perQuad);
201  return -1;
202  }
203  if ( perQuad < 1 )
204  {
205  lwerror("lwarc_linearize: segments per quadrant must be at least 1, got %d", perQuad);
206  return -1;
207  }
208  increment = fabs(M_PI_2 / perQuad);
209  LWDEBUGF(2, "lwarc_linearize: perQuad:%d, increment:%g (%g degrees)", perQuad, increment, increment*180/M_PI);
210 
211  }}
212  else if ( tolerance_type == LW_LINEARIZE_TOLERANCE_TYPE_MAX_DEVIATION )
213  {{
214  double halfAngle, maxErr;
215  if ( tol <= 0 )
216  {
217  lwerror("lwarc_linearize: max deviation must be bigger than 0, got %.15g", tol);
218  return -1;
219  }
220 
221  /*
222  * Ref: https://en.wikipedia.org/wiki/Sagitta_(geometry)
223  *
224  * An arc "sagitta" (distance between middle point of arc and
225  * middle point of corresponding chord) is defined as:
226  *
227  * sagitta = radius * ( 1 - cos( angle ) );
228  *
229  * We want our sagitta to be at most "tolerance" long,
230  * and we want to find out angle, so we use the inverse
231  * formula:
232  *
233  * tol = radius * ( 1 - cos( angle ) );
234  * 1 - cos( angle ) = tol/radius
235  * - cos( angle ) = tol/radius - 1
236  * cos( angle ) = - tol/radius + 1
237  * angle = acos( 1 - tol/radius )
238  *
239  * Constraints: 1.0 - tol/radius must be between -1 and 1
240  * which means tol must be between 0 and 2 times
241  * the radius, which makes sense as you cannot have a
242  * sagitta bigger than twice the radius!
243  *
244  */
245  maxErr = tol;
246  if ( maxErr > radius * 2 )
247  {
248  maxErr = radius * 2;
249  LWDEBUGF(2, "lwarc_linearize: tolerance %g is too big, "
250  "using arc-max 2 * radius == %g", tol, maxErr);
251  }
252  do {
253  halfAngle = acos( 1.0 - maxErr / radius );
254  /* TODO: avoid a loop here, going rather straight to
255  * a minimum angle value */
256  if ( halfAngle != 0 ) break;
257  LWDEBUGF(2, "lwarc_linearize: tolerance %g is too small for this arc"
258  " to compute approximation angle, doubling it", maxErr);
259  maxErr *= 2;
260  } while(1);
261  increment = 2 * halfAngle;
262  LWDEBUGF(2, "lwarc_linearize: maxDiff:%g, radius:%g, halfAngle:%g, increment:%g (%g degrees)", tol, radius, halfAngle, increment, increment*180/M_PI);
263  }}
264  else if ( tolerance_type == LW_LINEARIZE_TOLERANCE_TYPE_MAX_ANGLE )
265  {
266  increment = tol;
267  if ( increment <= 0 )
268  {
269  lwerror("lwarc_linearize: max angle must be bigger than 0, got %.15g", tol);
270  return -1;
271  }
272  }
273  else
274  {
275  lwerror("lwarc_linearize: unsupported tolerance type %d", tolerance_type);
276  return LW_FALSE;
277  }
278 
279  /* Angles of each point that defines the arc section */
280  a1 = atan2(p1->y - center.y, p1->x - center.x);
281  a2 = atan2(p2->y - center.y, p2->x - center.x);
282  a3 = atan2(p3->y - center.y, p3->x - center.x);
283 
284  LWDEBUGF(2, "lwarc_linearize A1:%g (%g) A2:%g (%g) A3:%g (%g)",
285  a1, a1*180/M_PI, a2, a2*180/M_PI, a3, a3*180/M_PI);
286 
287  if ( flags & LW_LINEARIZE_FLAG_SYMMETRIC )
288  {{
289  /* Calculate total arc angle, in radians */
290  double angle = clockwise ? a1 - a3 : a3 - a1;
291  if ( angle < 0 ) angle += M_PI * 2;
292  LWDEBUGF(2, "lwarc_linearize SYMMETRIC requested - total angle %g deg",
293  angle * 180 / M_PI);
294  if ( flags & LW_LINEARIZE_FLAG_RETAIN_ANGLE )
295  {{
296  /* Number of steps */
297  int steps = trunc(angle / increment);
298  /* Angle reminder */
299  double angle_reminder = angle - ( increment * steps );
300  angle_shift = angle_reminder / 2.0;
301 
302  LWDEBUGF(2, "lwarc_linearize RETAIN_ANGLE operation requested - "
303  "total angle %g, steps %d, increment %g, reminder %g",
304  angle * 180 / M_PI, steps, increment * 180 / M_PI,
305  angle_reminder * 180 / M_PI);
306  }}
307  else
308  {{
309  /* Number of segments in output */
310  int segs = ceil(angle / increment);
311  /* Tweak increment to be regular for all the arc */
312  increment = angle/segs;
313 
314  LWDEBUGF(2, "lwarc_linearize SYMMETRIC operation requested - "
315  "total angle %g degrees - LINESTRING(%g %g,%g %g,%g %g) - S:%d - I:%g",
316  angle*180/M_PI, p1->x, p1->y, center.x, center.y, p3->x, p3->y,
317  segs, increment*180/M_PI);
318  }}
319  }}
320 
321  /* p2 on left side => clockwise sweep */
322  if ( clockwise )
323  {
324  LWDEBUG(2, " Clockwise sweep");
325  increment *= -1;
326  angle_shift *= -1;
327  /* Adjust a3 down so we can decrement from a1 to a3 cleanly */
328  if ( a3 > a1 )
329  a3 -= 2.0 * M_PI;
330  if ( a2 > a1 )
331  a2 -= 2.0 * M_PI;
332  }
333  /* p2 on right side => counter-clockwise sweep */
334  else
335  {
336  LWDEBUG(2, " Counterclockwise sweep");
337  /* Adjust a3 up so we can increment from a1 to a3 cleanly */
338  if ( a3 < a1 )
339  a3 += 2.0 * M_PI;
340  if ( a2 < a1 )
341  a2 += 2.0 * M_PI;
342  }
343 
344  /* Override angles for circle case */
345  if( is_circle )
346  {
347  increment = fabs(increment);
348  a3 = a1 + 2.0 * M_PI;
349  a2 = a1 + M_PI;
350  clockwise = LW_FALSE;
351  }
352 
353  LWDEBUGF(2, "lwarc_linearize angle_shift:%g, increment:%g",
354  angle_shift * 180/M_PI, increment * 180/M_PI);
355 
356  if ( reverse ) {{
357  const int capacity = 8; /* TODO: compute exactly ? */
358  pa = ptarray_construct_empty(ptarray_has_z(to), ptarray_has_m(to), capacity);
359  }}
360 
361  /* Sweep from a1 to a3 */
362  if ( ! reverse )
363  {
365  }
366  ++points_added;
367  if ( angle_shift ) angle_shift -= increment;
368  LWDEBUGF(2, "a1:%g (%g deg), a3:%g (%g deg), inc:%g, shi:%g, cw:%d",
369  a1, a1 * 180 / M_PI, a3, a3 * 180 / M_PI, increment, angle_shift, clockwise);
370  for ( angle = a1 + increment + angle_shift; clockwise ? angle > a3 : angle < a3; angle += increment )
371  {
372  LWDEBUGF(2, " SA: %g ( %g deg )", angle, angle*180/M_PI);
373  pt.x = center.x + radius * cos(angle);
374  pt.y = center.y + radius * sin(angle);
375  pt.z = interpolate_arc(angle, a1, a2, a3, p1->z, p2->z, p3->z);
376  pt.m = interpolate_arc(angle, a1, a2, a3, p1->m, p2->m, p3->m);
377  ptarray_append_point(pa, &pt, LW_FALSE);
378  ++points_added;
379  angle_shift = 0;
380  }
381 
382  /* Ensure the final point is EXACTLY the same as the first for the circular case */
383  if ( is_circle )
384  {
385  ptarray_remove_point(pa, pa->npoints - 1);
387  }
388 
389  if ( reverse )
390  {
391  int i;
393  for ( i=pa->npoints; i>0; i-- ) {
394  getPoint4d_p(pa, i-1, &pt);
395  ptarray_append_point(to, &pt, LW_FALSE);
396  }
397  ptarray_free(pa);
398  }
399 
400  return points_added;
401 }
double x
Definition: liblwgeom.h:352
double lw_arc_center(const POINT2D *p1, const POINT2D *p2, const POINT2D *p3, POINT2D *result)
Determines the center of the circle defined by the three given points.
Definition: lwalgorithm.c:227
double m
Definition: liblwgeom.h:352
Tolerance expresses the maximum angle between the radii generating approximation line vertices...
Definition: liblwgeom.h:2217
int npoints
Definition: liblwgeom.h:371
POINTARRAY * ptarray_construct_empty(char hasz, char hasm, uint32_t maxpoints)
Create a new POINTARRAY with no points.
Definition: ptarray.c:70
void ptarray_free(POINTARRAY *pa)
Definition: ptarray.c:330
#define LWDEBUG(level, msg)
Definition: lwgeom_log.h:83
Tolerance expresses the maximum distance between an arbitrary point on the curve and the closest poin...
Definition: liblwgeom.h:2209
double x
Definition: liblwgeom.h:328
int ptarray_append_point(POINTARRAY *pa, const POINT4D *pt, int allow_duplicates)
Append a point to the end of an existing POINTARRAY If allow_duplicate is LW_FALSE, then a duplicate point will not be added.
Definition: ptarray.c:156
#define LW_FALSE
Definition: liblwgeom.h:77
#define LW_TRUE
Return types for functions with status returns.
Definition: liblwgeom.h:76
double y
Definition: liblwgeom.h:328
static double interpolate_arc(double angle, double a1, double a2, double a3, double zm1, double zm2, double zm3)
Definition: lwstroke.c:95
double z
Definition: liblwgeom.h:352
Symmetric linearization means that the output vertices would be the same no matter the order of the p...
Definition: liblwgeom.h:2226
Tolerance expresses the number of segments to use for each quarter of circle (quadrant).
Definition: liblwgeom.h:2202
int ptarray_has_m(const POINTARRAY *pa)
Definition: ptarray.c:43
int ptarray_remove_point(POINTARRAY *pa, int where)
Remove a point from an existing POINTARRAY.
Definition: ptarray.c:261
Retain angle instructs the engine to try its best to retain the requested angle between generating ra...
Definition: liblwgeom.h:2246
int ptarray_has_z(const POINTARRAY *pa)
Definition: ptarray.c:36
int lw_segment_side(const POINT2D *p1, const POINT2D *p2, const POINT2D *q)
lw_segment_side()
Definition: lwalgorithm.c:64
double y
Definition: liblwgeom.h:352
#define LWDEBUGF(level, msg,...)
Definition: lwgeom_log.h:88
void lwerror(const char *fmt,...)
Write a notice out to the error handler.
Definition: lwutil.c:190
int getPoint4d_p(const POINTARRAY *pa, int n, POINT4D *point)
Definition: lwgeom_api.c:122
Here is the call graph for this function:
Here is the caller graph for this function: