Coverage Report

Created: 2026-07-16 06:51

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/postgis/liblwgeom/lwstroke.c
Line
Count
Source
1
/**********************************************************************
2
 *
3
 * PostGIS - Spatial Types for PostgreSQL
4
 * http://postgis.net
5
 *
6
 * PostGIS is free software: you can redistribute it and/or modify
7
 * it under the terms of the GNU General Public License as published by
8
 * the Free Software Foundation, either version 2 of the License, or
9
 * (at your option) any later version.
10
 *
11
 * PostGIS is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
 * GNU General Public License for more details.
15
 *
16
 * You should have received a copy of the GNU General Public License
17
 * along with PostGIS.  If not, see <http://www.gnu.org/licenses/>.
18
 *
19
 **********************************************************************
20
 *
21
 * Copyright (C) 2001-2006 Refractions Research Inc.
22
 * Copyright (C) 2017      Sandro Santilli <strk@kbt.io>
23
 * Copyright (C) 2018      Daniel Baston <dbaston@gmail.com>
24
 *
25
 **********************************************************************/
26
27
28
#include <stdio.h>
29
#include <stdlib.h>
30
#include <stdarg.h>
31
#include <string.h>
32
33
#include "../postgis_config.h"
34
35
/*#define POSTGIS_DEBUG_LEVEL 3*/
36
37
#include "lwgeom_log.h"
38
39
#include "liblwgeom_internal.h"
40
41
LWGEOM *pta_unstroke(const POINTARRAY *points, int32_t srid);
42
LWGEOM* lwline_unstroke(const LWLINE *line);
43
LWGEOM* lwpolygon_unstroke(const LWPOLY *poly);
44
LWGEOM* lwmline_unstroke(const LWMLINE *mline);
45
LWGEOM* lwmpolygon_unstroke(const LWMPOLY *mpoly);
46
LWGEOM* lwcollection_unstroke(const LWCOLLECTION *c);
47
LWGEOM* lwgeom_unstroke(const LWGEOM *geom);
48
static LWLINE* lwnurbscurve_linearize(const LWNURBSCURVE *curve, double tol, LW_LINEARIZE_TOLERANCE_TYPE tolerance_type, int flags);
49
50
0
#define NURBS_MIN_LINEARIZE_SEGMENTS 8
51
0
#define NURBS_MAX_LINEARIZE_SEGMENTS 10000
52
53
/*
54
 * Determines (recursively in the case of collections) whether the geometry
55
 * contains at least on arc geometry or segment.
56
 */
57
int
58
lwgeom_has_arc(const LWGEOM *geom)
59
0
{
60
0
  LWCOLLECTION *col;
61
0
  uint32_t i;
62
63
0
  LWDEBUG(2, "lwgeom_has_arc called.");
64
65
0
  switch (geom->type)
66
0
  {
67
0
  case POINTTYPE:
68
0
  case LINETYPE:
69
0
  case POLYGONTYPE:
70
0
  case TRIANGLETYPE:
71
0
  case MULTIPOINTTYPE:
72
0
  case MULTILINETYPE:
73
0
  case MULTIPOLYGONTYPE:
74
0
  case POLYHEDRALSURFACETYPE:
75
0
  case TINTYPE:
76
0
    return LW_FALSE;
77
0
  case CIRCSTRINGTYPE:
78
0
  case NURBSCURVETYPE:
79
0
    return LW_TRUE;
80
  /* It's a collection that MAY contain an arc */
81
0
  default:
82
0
    col = (LWCOLLECTION *)geom;
83
0
    for (i=0; i<col->ngeoms; i++)
84
0
    {
85
0
      if (lwgeom_has_arc(col->geoms[i]) == LW_TRUE)
86
0
        return LW_TRUE;
87
0
    }
88
0
    return LW_FALSE;
89
0
  }
90
0
}
91
92
int
93
lwgeom_type_arc(const LWGEOM *geom)
94
0
{
95
0
  switch (geom->type)
96
0
  {
97
0
  case COMPOUNDTYPE:
98
0
  case CIRCSTRINGTYPE:
99
0
  case NURBSCURVETYPE:
100
0
  case CURVEPOLYTYPE:
101
0
  case MULTISURFACETYPE:
102
0
  case MULTICURVETYPE:
103
0
    return LW_TRUE;
104
0
  default:
105
0
    return LW_FALSE;
106
0
  }
107
0
}
108
109
/*******************************************************************************
110
 * Begin curve segmentize functions
111
 ******************************************************************************/
112
113
static double interpolate_arc(double angle, double a1, double a2, double a3, double zm1, double zm2, double zm3)
114
0
{
115
0
  LWDEBUGF(4,"angle %.05g a1 %.05g a2 %.05g a3 %.05g zm1 %.05g zm2 %.05g zm3 %.05g",angle,a1,a2,a3,zm1,zm2,zm3);
116
  /* Counter-clockwise sweep */
117
0
  if ( a1 < a2 )
118
0
  {
119
0
    if ( angle <= a2 )
120
0
      return zm1 + (zm2-zm1) * (angle-a1) / (a2-a1);
121
0
    else
122
0
      return zm2 + (zm3-zm2) * (angle-a2) / (a3-a2);
123
0
  }
124
  /* Clockwise sweep */
125
0
  else
126
0
  {
127
0
    if ( angle >= a2 )
128
0
      return zm1 + (zm2-zm1) * (a1-angle) / (a1-a2);
129
0
    else
130
0
      return zm2 + (zm3-zm2) * (a2-angle) / (a2-a3);
131
0
  }
132
0
}
133
134
/* Compute the angle covered by a single segment such that
135
 * a given number of segments per quadrant is achieved. */
136
static double angle_increment_using_segments_per_quad(double tol)
137
0
{
138
0
  double increment;
139
0
  int perQuad = rint(tol);
140
  // error out if tol != perQuad ? (not-round)
141
0
  if ( perQuad != tol )
142
0
  {
143
0
    lwerror("lwarc_linearize: segments per quadrant must be an integer value, got %.15g", tol);
144
0
    return -1;
145
0
  }
146
0
  if ( perQuad < 1 )
147
0
  {
148
0
    lwerror("lwarc_linearize: segments per quadrant must be at least 1, got %d", perQuad);
149
0
    return -1;
150
0
  }
151
0
  increment = fabs(M_PI_2 / perQuad);
152
0
  LWDEBUGF(2, "lwarc_linearize: perQuad:%d, increment:%g (%g degrees)", perQuad, increment, increment*180/M_PI);
153
154
0
  return increment;
155
0
}
156
157
/* Compute the angle covered by a single quadrant such that
158
 * the segment deviates from the arc by no more than a given
159
 * amount. */
160
static double angle_increment_using_max_deviation(double max_deviation, double radius)
161
0
{
162
0
  double increment, halfAngle, maxErr;
163
0
  if ( max_deviation <= 0 )
164
0
  {
165
0
    lwerror("lwarc_linearize: max deviation must be bigger than 0, got %.15g", max_deviation);
166
0
    return -1;
167
0
  }
168
169
  /*
170
   * Ref: https://en.wikipedia.org/wiki/Sagitta_(geometry)
171
   *
172
   * An arc "sagitta" (distance between middle point of arc and
173
   * middle point of corresponding chord) is defined as:
174
   *
175
   *   sagitta = radius * ( 1 - cos( angle ) );
176
   *
177
   * We want our sagitta to be at most "tolerance" long,
178
   * and we want to find out angle, so we use the inverse
179
   * formula:
180
   *
181
   *   tol = radius * ( 1 - cos( angle ) );
182
   *   1 - cos( angle ) =  tol/radius
183
   *   - cos( angle ) =  tol/radius - 1
184
   *   cos( angle ) =  - tol/radius + 1
185
   *   angle = acos( 1 - tol/radius )
186
   *
187
   * Constraints: 1.0 - tol/radius must be between -1 and 1
188
   * which means tol must be between 0 and 2 times
189
   * the radius, which makes sense as you cannot have a
190
   * sagitta bigger than twice the radius!
191
   *
192
   */
193
0
  maxErr = max_deviation;
194
0
  if ( maxErr > radius * 2 )
195
0
  {
196
0
    maxErr = radius * 2;
197
0
    LWDEBUGF(2,
198
0
       "lwarc_linearize: tolerance %g is too big, "
199
0
       "using arc-max 2 * radius == %g",
200
0
       max_deviation,
201
0
       maxErr);
202
0
  }
203
0
  do {
204
0
    halfAngle = acos( 1.0 - maxErr / radius );
205
    /* TODO: avoid a loop here, going rather straight to
206
     *       a minimum angle value */
207
0
    if ( halfAngle != 0 ) break;
208
0
    LWDEBUGF(2, "lwarc_linearize: tolerance %g is too small for this arc"
209
0
                " to compute approximation angle, doubling it", maxErr);
210
0
    maxErr *= 2;
211
0
  } while(1);
212
0
  increment = 2 * halfAngle;
213
0
  LWDEBUGF(2,
214
0
     "lwarc_linearize: maxDiff:%g, radius:%g, halfAngle:%g, increment:%g (%g degrees)",
215
0
     max_deviation,
216
0
     radius,
217
0
     halfAngle,
218
0
     increment,
219
0
     increment * 180 / M_PI);
220
221
0
  return increment;
222
0
}
223
224
/* Check that a given angle is positive and, if so, take
225
 * it to be the angle covered by a single segment. */
226
static double angle_increment_using_max_angle(double tol)
227
0
{
228
0
  if ( tol <= 0 )
229
0
  {
230
0
    lwerror("lwarc_linearize: max angle must be bigger than 0, got %.15g", tol);
231
0
    return -1;
232
0
  }
233
234
0
  return tol;
235
0
}
236
237
238
/**
239
 * Segmentize an arc
240
 *
241
 * Does not add the final vertex
242
 *
243
 * @param to POINTARRAY to append segmentized vertices to
244
 * @param p1 first point defining the arc
245
 * @param p2 second point defining the arc
246
 * @param p3 third point defining the arc
247
 * @param tol tolerance, semantic driven by tolerance_type
248
 * @param tolerance_type see LW_LINEARIZE_TOLERANCE_TYPE
249
 * @param flags LW_LINEARIZE_FLAGS
250
 *
251
 * @return number of points appended (0 if collinear),
252
 *         or -1 on error (lwerror would be called).
253
 *
254
 */
255
static int
256
lwarc_linearize(POINTARRAY *to,
257
                 const POINT4D *p1, const POINT4D *p2, const POINT4D *p3,
258
                 double tol, LW_LINEARIZE_TOLERANCE_TYPE tolerance_type,
259
                 int flags)
260
0
{
261
0
  POINT2D center;
262
0
  POINT2D *t1 = (POINT2D*)p1;
263
0
  POINT2D *t2 = (POINT2D*)p2;
264
0
  POINT2D *t3 = (POINT2D*)p3;
265
0
  POINT4D pt;
266
0
  int p2_side = 0;
267
0
  int clockwise = LW_TRUE;
268
0
  double radius; /* Arc radius */
269
0
  double increment; /* Angle per segment */
270
0
  double angle_shift = 0;
271
0
  double a1, a2, a3;
272
0
  POINTARRAY *pa;
273
0
  int is_circle = LW_FALSE;
274
0
  int points_added = 0;
275
0
  int reverse = 0;
276
0
  int segments = 0;
277
278
0
  LWDEBUG(2, "lwarc_linearize called.");
279
280
0
  LWDEBUGF(2, " curve is CIRCULARSTRING(%.15g %.15f, %.15f %.15f, %.15f %15f)",
281
0
    t1->x, t1->y, t2->x, t2->y, t3->x, t3->y);
282
283
0
  p2_side = lw_segment_side(t1, t3, t2);
284
285
0
  LWDEBUGF(2, " p2 side is %d", p2_side);
286
287
  /* Force counterclockwise scan if SYMMETRIC operation is requested */
288
0
  if ( p2_side == -1 && flags & LW_LINEARIZE_FLAG_SYMMETRIC )
289
0
  {
290
    /* swap p1-p3 */
291
0
    t1 = (POINT2D*)p3;
292
0
    t3 = (POINT2D*)p1;
293
0
    p1 = (POINT4D*)t1;
294
0
    p3 = (POINT4D*)t3;
295
0
    p2_side = 1;
296
0
    reverse = 1;
297
0
  }
298
299
0
  radius = lw_arc_center(t1, t2, t3, &center);
300
0
  LWDEBUGF(2, " center is POINT(%.15g %.15g) - radius:%g", center.x, center.y, radius);
301
302
  /* Matched start/end points imply circle */
303
0
  if ( p1->x == p3->x && p1->y == p3->y )
304
0
    is_circle = LW_TRUE;
305
306
  /* Negative radius signals straight line, p1/p2/p3 are collinear */
307
0
  if ( (radius < 0.0 || p2_side == 0) && ! is_circle )
308
0
      return 0;
309
310
  /* The side of the p1/p3 line that p2 falls on dictates the sweep
311
     direction from p1 to p3. */
312
0
  if ( p2_side == -1 )
313
0
    clockwise = LW_TRUE;
314
0
  else
315
0
    clockwise = LW_FALSE;
316
317
  /* Compute the increment (angle per segment) depending on
318
   * our tolerance type. */
319
0
  switch(tolerance_type)
320
0
  {
321
0
    case LW_LINEARIZE_TOLERANCE_TYPE_SEGS_PER_QUAD:
322
0
      increment = angle_increment_using_segments_per_quad(tol);
323
0
      break;
324
0
    case LW_LINEARIZE_TOLERANCE_TYPE_MAX_DEVIATION:
325
0
      increment = angle_increment_using_max_deviation(tol, radius);
326
0
      break;
327
0
    case LW_LINEARIZE_TOLERANCE_TYPE_MAX_ANGLE:
328
0
      increment = angle_increment_using_max_angle(tol);
329
0
      break;
330
0
    default:
331
0
      lwerror("lwarc_linearize: unsupported tolerance type %d", tolerance_type);
332
0
      return -1;
333
0
  }
334
335
0
  if (increment < 0)
336
0
  {
337
    /* Error occurred in increment calculation somewhere
338
     * (lwerror already called)
339
     */
340
0
    return -1;
341
0
  }
342
343
  /* Angles of each point that defines the arc section */
344
0
  a1 = atan2(p1->y - center.y, p1->x - center.x);
345
0
  a2 = atan2(p2->y - center.y, p2->x - center.x);
346
0
  a3 = atan2(p3->y - center.y, p3->x - center.x);
347
348
0
  LWDEBUGF(2, "lwarc_linearize A1:%g (%g) A2:%g (%g) A3:%g (%g)",
349
0
    a1, a1*180/M_PI, a2, a2*180/M_PI, a3, a3*180/M_PI);
350
351
  /* Calculate total arc angle, in radians */
352
0
  double total_angle = clockwise ? a1 - a3 : a3 - a1;
353
0
  if ( total_angle <= 0 ) total_angle += M_PI * 2;
354
355
  /* At extreme tolerance values (very low or very high, depending on
356
   * the semantic) we may cause our arc to collapse. In this case,
357
   * we want shrink the increment enough so that we get two segments
358
   * for a standard arc, or three segments for a complete circle. */
359
0
  int min_segs = is_circle ? 3 : 2;
360
0
  segments = ceil(total_angle / increment);
361
0
  if (segments < min_segs)
362
0
  {
363
0
    segments = min_segs;
364
0
    increment = total_angle / min_segs;
365
0
  }
366
367
0
  if ( flags & LW_LINEARIZE_FLAG_SYMMETRIC )
368
0
  {
369
0
    LWDEBUGF(2, "lwarc_linearize SYMMETRIC requested - total angle %g deg", total_angle * 180 / M_PI);
370
371
0
    if ( flags & LW_LINEARIZE_FLAG_RETAIN_ANGLE )
372
0
    {
373
      /* Number of complete steps */
374
0
      segments = trunc(total_angle / increment);
375
376
      /* Figure out the angle remainder, i.e. the amount of the angle
377
       * that is left after we can take no more complete angle
378
       * increments. */
379
0
      double angle_remainder = total_angle - (increment * segments);
380
381
      /* Shift the starting angle by half of the remainder. This
382
       * will have the effect of evenly distributing the remainder
383
       * among the first and last segments in the arc. */
384
0
      angle_shift = angle_remainder / 2.0;
385
386
0
      LWDEBUGF(2,
387
0
         "lwarc_linearize RETAIN_ANGLE operation requested - "
388
0
         "total angle %g, steps %d, increment %g, remainder %g",
389
0
         total_angle * 180 / M_PI,
390
0
         segments,
391
0
         increment * 180 / M_PI,
392
0
         angle_remainder * 180 / M_PI);
393
0
    }
394
0
    else
395
0
    {
396
      /* Number of segments in output */
397
0
      segments = ceil(total_angle / increment);
398
      /* Tweak increment to be regular for all the arc */
399
0
      increment = total_angle / segments;
400
401
0
      LWDEBUGF(2,
402
0
         "lwarc_linearize SYMMETRIC operation requested - "
403
0
         "total angle %g degrees - LINESTRING(%g %g,%g %g,%g %g) - S:%d -   I:%g",
404
0
         total_angle * 180 / M_PI,
405
0
         p1->x,
406
0
         p1->y,
407
0
         center.x,
408
0
         center.y,
409
0
         p3->x,
410
0
         p3->y,
411
0
         segments,
412
0
         increment * 180 / M_PI);
413
0
    }
414
0
  }
415
416
  /* p2 on left side => clockwise sweep */
417
0
  if ( clockwise )
418
0
  {
419
0
    LWDEBUG(2, " Clockwise sweep");
420
0
    increment *= -1;
421
0
    angle_shift *= -1;
422
    /* Adjust a3 down so we can decrement from a1 to a3 cleanly */
423
0
    if ( a3 > a1 )
424
0
      a3 -= 2.0 * M_PI;
425
0
    if ( a2 > a1 )
426
0
      a2 -= 2.0 * M_PI;
427
0
  }
428
  /* p2 on right side => counter-clockwise sweep */
429
0
  else
430
0
  {
431
0
    LWDEBUG(2, " Counterclockwise sweep");
432
    /* Adjust a3 up so we can increment from a1 to a3 cleanly */
433
0
    if ( a3 < a1 )
434
0
      a3 += 2.0 * M_PI;
435
0
    if ( a2 < a1 )
436
0
      a2 += 2.0 * M_PI;
437
0
  }
438
439
  /* Override angles for circle case */
440
0
  if( is_circle )
441
0
  {
442
0
    increment = fabs(increment);
443
0
    segments = ceil(total_angle / increment);
444
0
    if (segments < 3)
445
0
    {
446
0
      segments = 3;
447
0
      increment = total_angle / 3;
448
0
    }
449
0
    a3 = a1 + 2.0 * M_PI;
450
0
    a2 = a1 + M_PI;
451
0
    clockwise = LW_FALSE;
452
0
    angle_shift = 0.0;
453
0
  }
454
455
0
  LWDEBUGF(2, "lwarc_linearize angle_shift:%g, increment:%g",
456
0
    angle_shift * 180/M_PI, increment * 180/M_PI);
457
458
0
  if ( reverse )
459
0
  {
460
    /* Append points in order to a temporary POINTARRAY and
461
     * reverse them before writing to the output POINTARRAY. */
462
0
    const int capacity = 8; /* TODO: compute exactly ? */
463
0
    pa = ptarray_construct_empty(ptarray_has_z(to), ptarray_has_m(to), capacity);
464
0
  }
465
0
  else
466
0
  {
467
    /* Append points directly to the output POINTARRAY,
468
     * starting with p1. */
469
0
    pa = to;
470
471
0
    ptarray_append_point(pa, p1, LW_FALSE);
472
0
    ++points_added;
473
0
  }
474
475
  /* Sweep from a1 to a3 */
476
0
  int seg_start = 1; /* First point is added manually */
477
0
  int seg_end = segments;
478
0
  if (angle_shift != 0.0)
479
0
  {
480
    /* When we have extra angles we need to add the extra segments at the
481
     * start and end that cover those parts of the arc */
482
0
    seg_start = 0;
483
0
    seg_end = segments + 1;
484
0
  }
485
0
  LWDEBUGF(2, "a1:%g (%g deg), a3:%g (%g deg), inc:%g, shi:%g, cw:%d",
486
0
    a1, a1 * 180 / M_PI, a3, a3 * 180 / M_PI, increment, angle_shift, clockwise);
487
0
  for (int s = seg_start; s < seg_end; s++)
488
0
  {
489
0
    double angle = a1 + increment * s + angle_shift;
490
0
    LWDEBUGF(2, " SA: %g ( %g deg )", angle, angle*180/M_PI);
491
0
    pt.x = center.x + radius * cos(angle);
492
0
    pt.y = center.y + radius * sin(angle);
493
0
    pt.z = interpolate_arc(angle, a1, a2, a3, p1->z, p2->z, p3->z);
494
0
    pt.m = interpolate_arc(angle, a1, a2, a3, p1->m, p2->m, p3->m);
495
0
    ptarray_append_point(pa, &pt, LW_FALSE);
496
0
    ++points_added;
497
0
  }
498
499
  /* Ensure the final point is EXACTLY the same as the first for the circular case */
500
0
  if ( is_circle )
501
0
  {
502
0
    ptarray_remove_point(pa, pa->npoints - 1);
503
0
    ptarray_append_point(pa, p1, LW_FALSE);
504
0
  }
505
506
0
  if ( reverse )
507
0
  {
508
0
    int i;
509
0
    ptarray_append_point(to, p3, LW_FALSE);
510
0
    for ( i=pa->npoints; i>0; i-- ) {
511
0
      getPoint4d_p(pa, i-1, &pt);
512
0
      ptarray_append_point(to, &pt, LW_FALSE);
513
0
    }
514
0
    ptarray_free(pa);
515
0
  }
516
517
0
  return points_added;
518
0
}
519
520
/*
521
 * @param icurve input curve
522
 * @param tol tolerance, semantic driven by tolerance_type
523
 * @param tolerance_type see LW_LINEARIZE_TOLERANCE_TYPE
524
 * @param flags see flags in lwarc_linearize
525
 *
526
 * @return a newly allocated LWLINE
527
 */
528
static LWLINE *
529
lwcircstring_linearize(const LWCIRCSTRING *icurve, double tol,
530
                        LW_LINEARIZE_TOLERANCE_TYPE tolerance_type,
531
                        int flags)
532
0
{
533
0
  LWLINE *oline;
534
0
  POINTARRAY *ptarray;
535
0
  uint32_t i, j;
536
0
  POINT4D p1, p2, p3, p4;
537
0
  int ret;
538
539
0
  LWDEBUGF(2, "lwcircstring_linearize called., dim = %d", icurve->points->flags);
540
541
0
  ptarray = ptarray_construct_empty(FLAGS_GET_Z(icurve->points->flags), FLAGS_GET_M(icurve->points->flags), 64);
542
543
0
  for (i = 2; i < icurve->points->npoints; i+=2)
544
0
  {
545
0
    LWDEBUGF(3, "lwcircstring_linearize: arc ending at point %d", i);
546
547
0
    getPoint4d_p(icurve->points, i - 2, &p1);
548
0
    getPoint4d_p(icurve->points, i - 1, &p2);
549
0
    getPoint4d_p(icurve->points, i, &p3);
550
551
0
    ret = lwarc_linearize(ptarray, &p1, &p2, &p3, tol, tolerance_type, flags);
552
0
    if ( ret > 0 )
553
0
    {
554
0
      LWDEBUGF(3, "lwcircstring_linearize: generated %d points", ptarray->npoints);
555
0
    }
556
0
    else if ( ret == 0 )
557
0
    {
558
0
      LWDEBUG(3, "lwcircstring_linearize: points are colinear, returning curve points as line");
559
560
0
      for (j = i - 2 ; j < i ; j++)
561
0
      {
562
0
        getPoint4d_p(icurve->points, j, &p4);
563
0
        ptarray_append_point(ptarray, &p4, LW_TRUE);
564
0
      }
565
0
    }
566
0
    else
567
0
    {
568
      /* An error occurred, lwerror should have been called by now */
569
0
      ptarray_free(ptarray);
570
0
      return NULL;
571
0
    }
572
0
  }
573
0
  if (icurve->points->npoints > 0)
574
0
  {
575
0
    getPoint4d_p(icurve->points, icurve->points->npoints-1, &p1);
576
0
    ptarray_append_point(ptarray, &p1, LW_FALSE);
577
0
  }
578
579
0
  oline = lwline_construct(icurve->srid, NULL, ptarray);
580
0
  return oline;
581
0
}
582
583
/*
584
 * @param icompound input compound curve
585
 * @param tol tolerance, semantic driven by tolerance_type
586
 * @param tolerance_type see LW_LINEARIZE_TOLERANCE_TYPE
587
 * @param flags see flags in lwarc_linearize
588
 *
589
 * @return a newly allocated LWLINE
590
 */
591
static LWLINE *
592
lwcompound_linearize(const LWCOMPOUND *icompound, double tol,
593
                      LW_LINEARIZE_TOLERANCE_TYPE tolerance_type,
594
                      int flags)
595
0
{
596
0
  LWGEOM *geom;
597
0
  POINTARRAY *ptarray = NULL;
598
0
  LWLINE *tmp = NULL;
599
0
  uint32_t i, j;
600
0
  POINT4D p;
601
602
0
  LWDEBUG(2, "lwcompound_stroke called.");
603
604
0
  ptarray = ptarray_construct_empty(FLAGS_GET_Z(icompound->flags), FLAGS_GET_M(icompound->flags), 64);
605
606
0
  for (i = 0; i < icompound->ngeoms; i++)
607
0
  {
608
0
    geom = icompound->geoms[i];
609
0
    if (geom->type == CIRCSTRINGTYPE)
610
0
    {
611
0
      tmp = lwcircstring_linearize((LWCIRCSTRING *)geom, tol, tolerance_type, flags);
612
0
      for (j = 0; j < tmp->points->npoints; j++)
613
0
      {
614
0
        getPoint4d_p(tmp->points, j, &p);
615
0
        ptarray_append_point(ptarray, &p, LW_TRUE);
616
0
      }
617
0
      lwline_free(tmp);
618
0
    }
619
0
    else if (geom->type == LINETYPE)
620
0
    {
621
0
      tmp = (LWLINE *)geom;
622
0
      for (j = 0; j < tmp->points->npoints; j++)
623
0
      {
624
0
        getPoint4d_p(tmp->points, j, &p);
625
0
        ptarray_append_point(ptarray, &p, LW_TRUE);
626
0
      }
627
0
    }
628
0
    else if (geom->type == NURBSCURVETYPE)
629
0
    {
630
0
      tmp = lwnurbscurve_linearize((LWNURBSCURVE *)geom, tol, tolerance_type, flags);
631
0
      if (!tmp)
632
0
      {
633
0
        lwerror("%s: failed to linearize NURBS curve component", __func__);
634
0
        return NULL;
635
0
      }
636
0
      for (j = 0; j < tmp->points->npoints; j++)
637
0
      {
638
0
        getPoint4d_p(tmp->points, j, &p);
639
0
        ptarray_append_point(ptarray, &p, LW_TRUE);
640
0
      }
641
0
      lwline_free(tmp);
642
0
    }
643
0
    else
644
0
    {
645
0
      lwerror("%s: Unsupported geometry type: %s", __func__, lwtype_name(geom->type));
646
0
      return NULL;
647
0
    }
648
0
  }
649
650
0
  ptarray_remove_repeated_points_in_place(ptarray, 0.0, 2);
651
0
  return lwline_construct(icompound->srid, NULL, ptarray);
652
0
}
653
654
655
/*
656
 * @param icompound input curve polygon
657
 * @param tol tolerance, semantic driven by tolerance_type
658
 * @param tolerance_type see LW_LINEARIZE_TOLERANCE_TYPE
659
 * @param flags see flags in lwarc_linearize
660
 *
661
 * @return a newly allocated LWPOLY
662
 */
663
static LWPOLY *
664
lwcurvepoly_linearize(const LWCURVEPOLY *curvepoly, double tol,
665
                       LW_LINEARIZE_TOLERANCE_TYPE tolerance_type,
666
                       int flags)
667
0
{
668
0
  LWPOLY *ogeom;
669
0
  LWGEOM *tmp;
670
0
  LWLINE *line;
671
0
  POINTARRAY **ptarray;
672
0
  uint32_t i;
673
674
0
  LWDEBUG(2, "lwcurvepoly_linearize called.");
675
676
0
  ptarray = lwalloc(sizeof(POINTARRAY *)*curvepoly->nrings);
677
678
0
  for (i = 0; i < curvepoly->nrings; i++)
679
0
  {
680
0
    tmp = curvepoly->rings[i];
681
0
    if (tmp->type == CIRCSTRINGTYPE)
682
0
    {
683
0
      line = lwcircstring_linearize((LWCIRCSTRING *)tmp, tol, tolerance_type, flags);
684
0
      ptarray[i] = ptarray_clone_deep(line->points);
685
0
      lwline_free(line);
686
0
    }
687
0
    else if (tmp->type == LINETYPE)
688
0
    {
689
0
      line = (LWLINE *)tmp;
690
0
      ptarray[i] = ptarray_clone_deep(line->points);
691
0
    }
692
0
    else if (tmp->type == COMPOUNDTYPE)
693
0
    {
694
0
      line = lwcompound_linearize((LWCOMPOUND *)tmp, tol, tolerance_type, flags);
695
0
      ptarray[i] = ptarray_clone_deep(line->points);
696
0
      lwline_free(line);
697
0
    }
698
0
    else if (tmp->type == NURBSCURVETYPE)
699
0
    {
700
0
      line = lwnurbscurve_linearize((LWNURBSCURVE *)tmp, tol, tolerance_type, flags);
701
0
      if (!line)
702
0
      {
703
0
        lwerror("%s: failed to linearize NURBS curve ring", __func__);
704
0
        return NULL;
705
0
      }
706
0
      ptarray[i] = ptarray_clone_deep(line->points);
707
0
      lwline_free(line);
708
0
    }
709
0
    else
710
0
    {
711
0
      lwerror("Invalid ring type found in CurvePoly.");
712
0
      return NULL;
713
0
    }
714
0
  }
715
716
0
  ogeom = lwpoly_construct(curvepoly->srid, NULL, curvepoly->nrings, ptarray);
717
0
  return ogeom;
718
0
}
719
720
/* Kept for backward compatibility - TODO: drop */
721
LWPOLY *
722
lwcurvepoly_stroke(const LWCURVEPOLY *curvepoly, uint32_t perQuad)
723
0
{
724
0
    return lwcurvepoly_linearize(curvepoly, perQuad, LW_LINEARIZE_TOLERANCE_TYPE_SEGS_PER_QUAD, 0);
725
0
}
726
727
728
/**
729
 * @param mcurve input compound curve
730
 * @param tol tolerance, semantic driven by tolerance_type
731
 * @param tolerance_type see LW_LINEARIZE_TOLERANCE_TYPE
732
 * @param flags see flags in lwarc_linearize
733
 *
734
 * @return a newly allocated LWMLINE
735
 */
736
static LWMLINE *
737
lwmcurve_linearize(const LWMCURVE *mcurve, double tol,
738
                    LW_LINEARIZE_TOLERANCE_TYPE type,
739
                    int flags)
740
0
{
741
0
  LWMLINE *ogeom;
742
0
  LWGEOM **lines;
743
0
  uint32_t i;
744
745
0
  LWDEBUGF(2, "lwmcurve_linearize called, geoms=%d, dim=%d.", mcurve->ngeoms, FLAGS_NDIMS(mcurve->flags));
746
747
0
  lines = lwalloc(sizeof(LWGEOM *)*mcurve->ngeoms);
748
749
0
  for (i = 0; i < mcurve->ngeoms; i++)
750
0
  {
751
0
    const LWGEOM *tmp = mcurve->geoms[i];
752
0
    if (tmp->type == CIRCSTRINGTYPE)
753
0
    {
754
0
      lines[i] = (LWGEOM *)lwcircstring_linearize((LWCIRCSTRING *)tmp, tol, type, flags);
755
0
    }
756
0
    else if (tmp->type == LINETYPE)
757
0
    {
758
0
      lines[i] = (LWGEOM *)lwline_construct(mcurve->srid, NULL, ptarray_clone_deep(((LWLINE *)tmp)->points));
759
0
    }
760
0
    else if (tmp->type == COMPOUNDTYPE)
761
0
    {
762
0
      lines[i] = (LWGEOM *)lwcompound_linearize((LWCOMPOUND *)tmp, tol, type, flags);
763
0
    }
764
0
    else if (tmp->type == NURBSCURVETYPE)
765
0
    {
766
0
      lines[i] = (LWGEOM *)lwnurbscurve_linearize((LWNURBSCURVE *)tmp, tol, type, flags);
767
0
      if (!lines[i])
768
0
      {
769
0
        lwerror("%s: failed to linearize NURBS curve component", __func__);
770
0
        return NULL;
771
0
      }
772
0
    }
773
0
    else
774
0
    {
775
0
      lwerror("Unsupported geometry found in MultiCurve.");
776
0
      return NULL;
777
0
    }
778
0
  }
779
780
0
  ogeom = (LWMLINE *)lwcollection_construct(MULTILINETYPE, mcurve->srid, NULL, mcurve->ngeoms, lines);
781
0
  return ogeom;
782
0
}
783
784
/**
785
 * @param msurface input multi surface
786
 * @param tol tolerance, semantic driven by tolerance_type
787
 * @param tolerance_type see LW_LINEARIZE_TOLERANCE_TYPE
788
 * @param flags see flags in lwarc_linearize
789
 *
790
 * @return a newly allocated LWMPOLY
791
 */
792
static LWMPOLY *
793
lwmsurface_linearize(const LWMSURFACE *msurface, double tol,
794
                      LW_LINEARIZE_TOLERANCE_TYPE type,
795
                      int flags)
796
0
{
797
0
  LWMPOLY *ogeom;
798
0
  LWGEOM *tmp;
799
0
  LWPOLY *poly;
800
0
  LWGEOM **polys;
801
0
  POINTARRAY **ptarray;
802
0
  uint32_t i, j;
803
804
0
  LWDEBUG(2, "lwmsurface_linearize called.");
805
806
0
  polys = lwalloc(sizeof(LWGEOM *)*msurface->ngeoms);
807
808
0
  for (i = 0; i < msurface->ngeoms; i++)
809
0
  {
810
0
    tmp = msurface->geoms[i];
811
0
    if (tmp->type == CURVEPOLYTYPE)
812
0
    {
813
0
      polys[i] = (LWGEOM *)lwcurvepoly_linearize((LWCURVEPOLY *)tmp, tol, type, flags);
814
0
    }
815
0
    else if (tmp->type == POLYGONTYPE)
816
0
    {
817
0
      poly = (LWPOLY *)tmp;
818
0
      ptarray = lwalloc(sizeof(POINTARRAY *)*poly->nrings);
819
0
      for (j = 0; j < poly->nrings; j++)
820
0
      {
821
0
        ptarray[j] = ptarray_clone_deep(poly->rings[j]);
822
0
      }
823
0
      polys[i] = (LWGEOM *)lwpoly_construct(msurface->srid, NULL, poly->nrings, ptarray);
824
0
    }
825
0
  }
826
0
  ogeom = (LWMPOLY *)lwcollection_construct(MULTIPOLYGONTYPE, msurface->srid, NULL, msurface->ngeoms, polys);
827
0
  return ogeom;
828
0
}
829
830
/**
831
 * @param collection input geometry collection
832
 * @param tol tolerance, semantic driven by tolerance_type
833
 * @param tolerance_type see LW_LINEARIZE_TOLERANCE_TYPE
834
 * @param flags see flags in lwarc_linearize
835
 *
836
 * @return a newly allocated LWCOLLECTION
837
 */
838
static LWCOLLECTION *
839
lwcollection_linearize(const LWCOLLECTION *collection, double tol,
840
                    LW_LINEARIZE_TOLERANCE_TYPE type,
841
                    int flags)
842
0
{
843
0
  LWCOLLECTION *ocol;
844
0
  LWGEOM *tmp;
845
0
  LWGEOM **geoms;
846
0
  uint32_t i;
847
848
0
  LWDEBUG(2, "lwcollection_linearize called.");
849
850
0
  geoms = lwalloc(sizeof(LWGEOM *)*collection->ngeoms);
851
852
0
  for (i=0; i<collection->ngeoms; i++)
853
0
  {
854
0
    tmp = collection->geoms[i];
855
0
    switch (tmp->type)
856
0
    {
857
0
    case CIRCSTRINGTYPE:
858
0
      geoms[i] = (LWGEOM *)lwcircstring_linearize((LWCIRCSTRING *)tmp, tol, type, flags);
859
0
      break;
860
0
    case NURBSCURVETYPE:
861
0
      geoms[i] = (LWGEOM *)lwnurbscurve_linearize((LWNURBSCURVE *)tmp, tol, type, flags);
862
0
      if (!geoms[i])
863
0
      {
864
0
        lwerror("%s: failed to linearize NURBS curve element", __func__);
865
0
        return NULL;
866
0
      }
867
0
      break;
868
0
    case COMPOUNDTYPE:
869
0
      geoms[i] = (LWGEOM *)lwcompound_linearize((LWCOMPOUND *)tmp, tol, type, flags);
870
0
      break;
871
0
    case CURVEPOLYTYPE:
872
0
      geoms[i] = (LWGEOM *)lwcurvepoly_linearize((LWCURVEPOLY *)tmp, tol, type, flags);
873
0
      break;
874
0
    case MULTICURVETYPE:
875
0
    case MULTISURFACETYPE:
876
0
    case COLLECTIONTYPE:
877
0
      geoms[i] = (LWGEOM *)lwcollection_linearize((LWCOLLECTION *)tmp, tol, type, flags);
878
0
      break;
879
0
    default:
880
0
      geoms[i] = lwgeom_clone_deep(tmp);
881
0
      break;
882
0
    }
883
0
  }
884
0
  ocol = lwcollection_construct(COLLECTIONTYPE, collection->srid, NULL, collection->ngeoms, geoms);
885
0
  return ocol;
886
0
}
887
888
/*
889
 * @param curve input NURBS curve
890
 * @param tol tolerance, semantic driven by tolerance_type
891
 * @param tolerance_type see LW_LINEARIZE_TOLERANCE_TYPE
892
 * @param flags see flags in lwarc_linearize
893
 *
894
 * @return a newly allocated LWLINE
895
 */
896
static LWLINE *
897
lwnurbscurve_linearize(const LWNURBSCURVE *curve, double tol,
898
                        LW_LINEARIZE_TOLERANCE_TYPE tolerance_type,
899
                        int flags)
900
0
{
901
0
  uint32_t num_segments;
902
0
  const double max_segments = (double)UINT32_MAX - 1.0;
903
904
0
  LWDEBUG(2, "lwnurbscurve_linearize called.");
905
906
0
  if (!curve)
907
0
    return NULL;
908
909
0
  if (!isfinite(tol) || tol <= 0.0)
910
0
  {
911
0
    lwerror("%s: tolerance must be finite and > 0, got %.15g", __func__, tol);
912
0
    return NULL;
913
0
  }
914
915
0
  if (!curve->points || curve->points->npoints == 0)
916
0
    return lwnurbscurve_to_linestring(curve, NURBS_MIN_LINEARIZE_SEGMENTS);
917
918
0
  switch (tolerance_type)
919
0
  {
920
0
  case LW_LINEARIZE_TOLERANCE_TYPE_SEGS_PER_QUAD:
921
0
    if (tol != rint(tol))
922
0
    {
923
0
      lwerror("%s: segs-per-quad must be an integer, got %.15g", __func__, tol);
924
0
      return NULL;
925
0
    }
926
0
    if (tol > ((double)NURBS_MAX_LINEARIZE_SEGMENTS / 4.0))
927
0
    {
928
0
      lwerror("%s: segs-per-quad is too large, got %.15g", __func__, tol);
929
0
      return NULL;
930
0
    }
931
0
    num_segments = (uint32_t)(tol * 4);
932
0
    break;
933
0
  case LW_LINEARIZE_TOLERANCE_TYPE_MAX_DEVIATION: {
934
0
    GBOX box;
935
0
    double width = 0.0;
936
0
    double height = 0.0;
937
0
    double depth = 0.0;
938
0
    double diagonal = 0.0;
939
0
    double segments_required;
940
941
0
    if (ptarray_calculate_gbox_cartesian(curve->points, &box) == LW_SUCCESS)
942
0
    {
943
0
      width = box.xmax - box.xmin;
944
0
      height = box.ymax - box.ymin;
945
0
      if (FLAGS_GET_Z(curve->flags))
946
0
        depth = box.zmax - box.zmin;
947
0
      diagonal = sqrt(width * width + height * height + depth * depth);
948
0
    }
949
950
0
    segments_required = diagonal / tol;
951
0
    if (!isfinite(segments_required) || segments_required > max_segments)
952
0
    {
953
0
      lwerror("%s: max deviation is too small, got %.15g", __func__, tol);
954
0
      return NULL;
955
0
    }
956
0
    num_segments = diagonal > 0.0 ? (uint32_t)ceil(segments_required) : NURBS_MIN_LINEARIZE_SEGMENTS;
957
0
    break;
958
0
  }
959
0
  case LW_LINEARIZE_TOLERANCE_TYPE_MAX_ANGLE:
960
0
    if (tol < (2.0 * M_PI) / max_segments)
961
0
    {
962
0
      lwerror("%s: max angle is too small, got %.15g", __func__, tol);
963
0
      return NULL;
964
0
    }
965
0
    num_segments = (uint32_t)ceil((2.0 * M_PI) / tol);
966
0
    break;
967
0
  default:
968
0
    lwerror("%s: unsupported tolerance type %d", __func__, tolerance_type);
969
0
    return NULL;
970
0
  }
971
972
0
  if (num_segments < NURBS_MIN_LINEARIZE_SEGMENTS)
973
0
    num_segments = NURBS_MIN_LINEARIZE_SEGMENTS;
974
0
  if (num_segments > NURBS_MAX_LINEARIZE_SEGMENTS)
975
0
  {
976
0
    lwerror("%s: requested too many segments, got %u", __func__, (unsigned int)num_segments);
977
0
    return NULL;
978
0
  }
979
980
0
  (void)flags; /* Currently unused for NURBS linearization */
981
982
0
  return lwnurbscurve_to_linestring(curve, num_segments);
983
0
}
984
985
LWGEOM *
986
lwcurve_linearize(const LWGEOM *geom, double tol,
987
                  LW_LINEARIZE_TOLERANCE_TYPE type,
988
                  int flags)
989
0
{
990
0
  LWGEOM * ogeom = NULL;
991
0
  switch (geom->type)
992
0
  {
993
0
  case CIRCSTRINGTYPE:
994
0
    ogeom = (LWGEOM *)lwcircstring_linearize((LWCIRCSTRING *)geom, tol, type, flags);
995
0
    break;
996
0
  case NURBSCURVETYPE:
997
0
    ogeom = (LWGEOM *)lwnurbscurve_linearize((LWNURBSCURVE *)geom, tol, type, flags);
998
0
    break;
999
0
  case COMPOUNDTYPE:
1000
0
    ogeom = (LWGEOM *)lwcompound_linearize((LWCOMPOUND *)geom, tol, type, flags);
1001
0
    break;
1002
0
  case CURVEPOLYTYPE:
1003
0
    ogeom = (LWGEOM *)lwcurvepoly_linearize((LWCURVEPOLY *)geom, tol, type, flags);
1004
0
    break;
1005
0
  case MULTICURVETYPE:
1006
0
    ogeom = (LWGEOM *)lwmcurve_linearize((LWMCURVE *)geom, tol, type, flags);
1007
0
    break;
1008
0
  case MULTISURFACETYPE:
1009
0
    ogeom = (LWGEOM *)lwmsurface_linearize((LWMSURFACE *)geom, tol, type, flags);
1010
0
    break;
1011
0
  case COLLECTIONTYPE:
1012
0
    ogeom = (LWGEOM *)lwcollection_linearize((LWCOLLECTION *)geom, tol, type, flags);
1013
0
    break;
1014
0
  default:
1015
0
    ogeom = lwgeom_clone_deep(geom);
1016
0
  }
1017
0
  return ogeom;
1018
0
}
1019
1020
/* Kept for backward compatibility - TODO: drop */
1021
LWGEOM *
1022
lwgeom_stroke(const LWGEOM *geom, uint32_t perQuad)
1023
0
{
1024
0
  return lwcurve_linearize(geom, perQuad, LW_LINEARIZE_TOLERANCE_TYPE_SEGS_PER_QUAD, 0);
1025
0
}
1026
1027
/**
1028
 * Return ABC angle in radians
1029
 * TODO: move to lwalgorithm
1030
 */
1031
static double
1032
lw_arc_angle(const POINT2D *a, const POINT2D *b, const POINT2D *c)
1033
0
{
1034
0
  POINT2D ab, cb;
1035
1036
0
  ab.x = b->x - a->x;
1037
0
  ab.y = b->y - a->y;
1038
1039
0
  cb.x = b->x - c->x;
1040
0
  cb.y = b->y - c->y;
1041
1042
0
  double dot = (ab.x * cb.x + ab.y * cb.y); /* dot product */
1043
0
  double cross = (ab.x * cb.y - ab.y * cb.x); /* cross product */
1044
1045
0
  double alpha = atan2(cross, dot);
1046
1047
0
  return alpha;
1048
0
}
1049
1050
/**
1051
* Returns LW_TRUE if b is on the arc formed by a1/a2/a3, but not within
1052
* that portion already described by a1/a2/a3
1053
*/
1054
static int pt_continues_arc(const POINT4D *a1, const POINT4D *a2, const POINT4D *a3, const POINT4D *b)
1055
0
{
1056
0
  POINT2D center;
1057
0
  POINT2D *t1 = (POINT2D*)a1;
1058
0
  POINT2D *t2 = (POINT2D*)a2;
1059
0
  POINT2D *t3 = (POINT2D*)a3;
1060
0
  POINT2D *tb = (POINT2D*)b;
1061
0
  double radius = lw_arc_center(t1, t2, t3, &center);
1062
0
  double b_distance, diff;
1063
1064
  /* Co-linear a1/a2/a3 */
1065
0
  if ( radius < 0.0 )
1066
0
    return LW_FALSE;
1067
1068
0
  b_distance = distance2d_pt_pt(tb, &center);
1069
0
  diff = fabs(radius - b_distance);
1070
0
  LWDEBUGF(4, "circle_radius=%g, b_distance=%g, diff=%g, percentage=%g", radius, b_distance, diff, diff/radius);
1071
1072
  /* Is the point b on the circle? */
1073
0
  if ( diff < EPSILON_SQLMM )
1074
0
  {
1075
0
    int a2_side = lw_segment_side(t1, t3, t2);
1076
0
    int b_side  = lw_segment_side(t1, t3, tb);
1077
0
    double angle1 = lw_arc_angle(t1, t2, t3);
1078
0
    double angle2 = lw_arc_angle(t2, t3, tb);
1079
1080
    /* Is the angle similar to the previous one ? */
1081
0
    diff = fabs(angle1 - angle2);
1082
0
    LWDEBUGF(4, " angle1: %g, angle2: %g, diff:%g", angle1, angle2, diff);
1083
0
    if ( diff > EPSILON_SQLMM )
1084
0
    {
1085
0
      return LW_FALSE;
1086
0
    }
1087
1088
    /* Is the point b on the same side of a1/a3 as the mid-point a2 is? */
1089
    /* If not, it's in the unbounded part of the circle, so it continues the arc, return true. */
1090
0
    if ( b_side != a2_side )
1091
0
      return LW_TRUE;
1092
0
  }
1093
0
  return LW_FALSE;
1094
0
}
1095
1096
static LWGEOM *
1097
linestring_from_pa(const POINTARRAY *pa, int32_t srid, int start, int end)
1098
0
{
1099
0
  int i = 0, j = 0;
1100
0
  POINT4D p;
1101
0
  POINTARRAY *pao = ptarray_construct(ptarray_has_z(pa), ptarray_has_m(pa), end-start+2);
1102
0
  LWDEBUGF(4, "srid=%d, start=%d, end=%d", srid, start, end);
1103
0
  for( i = start; i < end + 2; i++ )
1104
0
  {
1105
0
    getPoint4d_p(pa, i, &p);
1106
0
    ptarray_set_point4d(pao, j++, &p);
1107
0
  }
1108
0
  return lwline_as_lwgeom(lwline_construct(srid, NULL, pao));
1109
0
}
1110
1111
static LWGEOM *
1112
circstring_from_pa(const POINTARRAY *pa, int32_t srid, int start, int end)
1113
0
{
1114
1115
0
  POINT4D p0, p1, p2;
1116
0
  POINTARRAY *pao = ptarray_construct(ptarray_has_z(pa), ptarray_has_m(pa), 3);
1117
0
  LWDEBUGF(4, "srid=%d, start=%d, end=%d", srid, start, end);
1118
0
  getPoint4d_p(pa, start, &p0);
1119
0
  ptarray_set_point4d(pao, 0, &p0);
1120
0
  getPoint4d_p(pa, (start+end+1)/2, &p1);
1121
0
  ptarray_set_point4d(pao, 1, &p1);
1122
0
  getPoint4d_p(pa, end+1, &p2);
1123
0
  ptarray_set_point4d(pao, 2, &p2);
1124
0
  return lwcircstring_as_lwgeom(lwcircstring_construct(srid, NULL, pao));
1125
0
}
1126
1127
static LWGEOM *
1128
geom_from_pa(const POINTARRAY *pa, int32_t srid, int is_arc, int start, int end)
1129
0
{
1130
0
  LWDEBUGF(4, "srid=%d, is_arc=%d, start=%d, end=%d", srid, is_arc, start, end);
1131
0
  if ( is_arc )
1132
0
    return circstring_from_pa(pa, srid, start, end);
1133
0
  else
1134
0
    return linestring_from_pa(pa, srid, start, end);
1135
0
}
1136
1137
LWGEOM *
1138
pta_unstroke(const POINTARRAY *points, int32_t srid)
1139
0
{
1140
0
  int i = 0, j, k;
1141
0
  POINT4D a1, a2, a3, b;
1142
0
  POINT4D first, center;
1143
0
  char *edges_in_arcs;
1144
0
  int found_arc = LW_FALSE;
1145
0
  int current_arc = 1;
1146
0
  int num_edges;
1147
0
  int edge_type; /* non-zero if edge is part of an arc */
1148
0
  int start, end;
1149
0
  LWCOLLECTION *outcol;
1150
  /* Minimum number of edges, per quadrant, required to define an arc */
1151
0
  const unsigned int min_quad_edges = 2;
1152
1153
  /* Die on null input */
1154
0
  if ( ! points )
1155
0
    lwerror("pta_unstroke called with null pointarray");
1156
1157
  /* Null on empty input? */
1158
0
  if ( points->npoints == 0 )
1159
0
    return NULL;
1160
1161
  /* We can't desegmentize anything shorter than four points */
1162
0
  if ( points->npoints < 4 )
1163
0
  {
1164
    /* Return a linestring here*/
1165
0
    lwerror("pta_unstroke needs implementation for npoints < 4");
1166
0
  }
1167
1168
  /* Allocate our result array of vertices that are part of arcs */
1169
0
  num_edges = points->npoints - 1;
1170
0
  edges_in_arcs = lwalloc(num_edges + 1);
1171
0
  memset(edges_in_arcs, 0, num_edges + 1);
1172
1173
  /* We make a candidate arc of the first two edges, */
1174
  /* And then see if the next edge follows it */
1175
0
  while( i < num_edges-2 )
1176
0
  {
1177
0
    unsigned int arc_edges;
1178
0
    double num_quadrants;
1179
0
    double angle;
1180
1181
0
    found_arc = LW_FALSE;
1182
    /* Make candidate arc */
1183
0
    getPoint4d_p(points, i  , &a1);
1184
0
    getPoint4d_p(points, i+1, &a2);
1185
0
    getPoint4d_p(points, i+2, &a3);
1186
0
    memcpy(&first, &a1, sizeof(POINT4D));
1187
1188
0
    for( j = i+3; j < num_edges+1; j++ )
1189
0
    {
1190
0
      LWDEBUGF(4, "i=%d, j=%d", i, j);
1191
0
      getPoint4d_p(points, j, &b);
1192
      /* Does this point fall on our candidate arc? */
1193
0
      if ( pt_continues_arc(&a1, &a2, &a3, &b) )
1194
0
      {
1195
        /* Yes. Mark this edge and the two preceding it as arc components */
1196
0
        LWDEBUGF(4, "pt_continues_arc #%d", current_arc);
1197
0
        found_arc = LW_TRUE;
1198
0
        for ( k = j-1; k > j-4; k-- )
1199
0
          edges_in_arcs[k] = current_arc;
1200
0
      }
1201
0
      else
1202
0
      {
1203
        /* No. So we're done with this candidate arc */
1204
0
        LWDEBUG(4, "pt_continues_arc = false");
1205
0
        current_arc++;
1206
0
        break;
1207
0
      }
1208
1209
0
      memcpy(&a1, &a2, sizeof(POINT4D));
1210
0
      memcpy(&a2, &a3, sizeof(POINT4D));
1211
0
      memcpy(&a3,  &b, sizeof(POINT4D));
1212
0
    }
1213
    /* Jump past all the edges that were added to the arc */
1214
0
    if ( found_arc )
1215
0
    {
1216
      /* Check if an arc was composed by enough edges to be
1217
       * really considered an arc
1218
       * See http://trac.osgeo.org/postgis/ticket/2420
1219
       */
1220
0
      arc_edges = j - 1 - i;
1221
0
      LWDEBUGF(4, "arc defined by %d edges found", arc_edges);
1222
0
      if ( first.x == b.x && first.y == b.y ) {
1223
0
        LWDEBUG(4, "arc is a circle");
1224
0
        num_quadrants = 4;
1225
0
      }
1226
0
      else {
1227
0
        lw_arc_center((POINT2D*)&first, (POINT2D*)&b, (POINT2D*)&a1, (POINT2D*)&center);
1228
0
        angle = lw_arc_angle((POINT2D*)&first, (POINT2D*)&center, (POINT2D*)&b);
1229
0
        int p2_side = lw_segment_side((POINT2D*)&first, (POINT2D*)&a1, (POINT2D*)&b);
1230
0
        if ( p2_side >= 0 ) angle = -angle;
1231
1232
0
        if ( angle < 0 ) angle = 2 * M_PI + angle;
1233
0
        num_quadrants = ( 4 * angle ) / ( 2 * M_PI );
1234
0
        LWDEBUGF(4, "arc angle (%g %g, %g %g, %g %g) is %g (side is %d), quadrants:%g", first.x, first.y, center.x, center.y, b.x, b.y, angle, p2_side, num_quadrants);
1235
0
      }
1236
      /* a1 is first point, b is last point */
1237
0
      if ( arc_edges < min_quad_edges * num_quadrants ) {
1238
0
        LWDEBUGF(4, "Not enough edges for a %g quadrants arc, %g needed", num_quadrants, min_quad_edges * num_quadrants);
1239
0
        for ( k = j-1; k >= i; k-- )
1240
0
          edges_in_arcs[k] = 0;
1241
0
      }
1242
1243
0
      i = j-1;
1244
0
    }
1245
0
    else
1246
0
    {
1247
      /* Mark this edge as a linear edge */
1248
0
      edges_in_arcs[i] = 0;
1249
0
      i = i+1;
1250
0
    }
1251
0
  }
1252
1253
#if POSTGIS_DEBUG_LEVEL > 3
1254
  {
1255
    char *edgestr = lwalloc(num_edges+1);
1256
    for ( i = 0; i < num_edges; i++ )
1257
    {
1258
      if ( edges_in_arcs[i] )
1259
        edgestr[i] = 48 + edges_in_arcs[i];
1260
      else
1261
        edgestr[i] = '.';
1262
    }
1263
    edgestr[num_edges] = 0;
1264
    LWDEBUGF(3, "edge pattern %s", edgestr);
1265
    lwfree(edgestr);
1266
  }
1267
#endif
1268
1269
0
  start = 0;
1270
0
  edge_type = edges_in_arcs[0];
1271
0
  outcol = lwcollection_construct_empty(COMPOUNDTYPE, srid, ptarray_has_z(points), ptarray_has_m(points));
1272
0
  for( i = 1; i < num_edges; i++ )
1273
0
  {
1274
0
    if( edge_type != edges_in_arcs[i] )
1275
0
    {
1276
0
      end = i - 1;
1277
0
      lwcollection_add_lwgeom(outcol, geom_from_pa(points, srid, edge_type, start, end));
1278
0
      start = i;
1279
0
      edge_type = edges_in_arcs[i];
1280
0
    }
1281
0
  }
1282
0
  lwfree(edges_in_arcs); /* not needed anymore */
1283
1284
  /* Roll out last item */
1285
0
  end = num_edges - 1;
1286
0
  lwcollection_add_lwgeom(outcol, geom_from_pa(points, srid, edge_type, start, end));
1287
1288
  /* Strip down to singleton if only one entry */
1289
0
  if ( outcol->ngeoms == 1 )
1290
0
  {
1291
0
    LWGEOM *outgeom = outcol->geoms[0];
1292
0
    outcol->ngeoms = 0; lwcollection_free(outcol);
1293
0
    return outgeom;
1294
0
  }
1295
0
  return lwcollection_as_lwgeom(outcol);
1296
0
}
1297
1298
1299
LWGEOM *
1300
lwline_unstroke(const LWLINE *line)
1301
0
{
1302
0
  LWDEBUG(2, "lwline_unstroke called.");
1303
1304
0
  if ( line->points->npoints < 4 ) return lwline_as_lwgeom(lwline_clone_deep(line));
1305
0
  else return pta_unstroke(line->points, line->srid);
1306
0
}
1307
1308
LWGEOM *
1309
lwpolygon_unstroke(const LWPOLY *poly)
1310
0
{
1311
0
  LWGEOM **geoms;
1312
0
  uint32_t i, hascurve = 0;
1313
1314
0
  LWDEBUG(2, "lwpolygon_unstroke called.");
1315
1316
0
  geoms = lwalloc(sizeof(LWGEOM *)*poly->nrings);
1317
0
  for (i=0; i<poly->nrings; i++)
1318
0
  {
1319
0
    geoms[i] = pta_unstroke(poly->rings[i], poly->srid);
1320
0
    if (geoms[i]->type == CIRCSTRINGTYPE || geoms[i]->type == COMPOUNDTYPE)
1321
0
    {
1322
0
      hascurve = 1;
1323
0
    }
1324
0
  }
1325
0
  if (hascurve == 0)
1326
0
  {
1327
0
    for (i=0; i<poly->nrings; i++)
1328
0
    {
1329
0
      lwfree(geoms[i]); /* TODO: should this be lwgeom_free instead ? */
1330
0
    }
1331
0
    return lwgeom_clone_deep((LWGEOM *)poly);
1332
0
  }
1333
1334
0
  return (LWGEOM *)lwcollection_construct(CURVEPOLYTYPE, poly->srid, NULL, poly->nrings, geoms);
1335
0
}
1336
1337
LWGEOM *
1338
lwmline_unstroke(const LWMLINE *mline)
1339
0
{
1340
0
  LWGEOM **geoms;
1341
0
  uint32_t i, hascurve = 0;
1342
1343
0
  LWDEBUG(2, "lwmline_unstroke called.");
1344
1345
0
  geoms = lwalloc(sizeof(LWGEOM *)*mline->ngeoms);
1346
0
  for (i=0; i<mline->ngeoms; i++)
1347
0
  {
1348
0
    geoms[i] = lwline_unstroke((LWLINE *)mline->geoms[i]);
1349
0
    if (geoms[i]->type == CIRCSTRINGTYPE || geoms[i]->type == COMPOUNDTYPE)
1350
0
    {
1351
0
      hascurve = 1;
1352
0
    }
1353
0
  }
1354
0
  if (hascurve == 0)
1355
0
  {
1356
0
    for (i=0; i<mline->ngeoms; i++)
1357
0
    {
1358
0
      lwfree(geoms[i]); /* TODO: should this be lwgeom_free instead ? */
1359
0
    }
1360
0
    return lwgeom_clone_deep((LWGEOM *)mline);
1361
0
  }
1362
0
  return (LWGEOM *)lwcollection_construct(MULTICURVETYPE, mline->srid, NULL, mline->ngeoms, geoms);
1363
0
}
1364
1365
LWGEOM *
1366
lwmpolygon_unstroke(const LWMPOLY *mpoly)
1367
0
{
1368
0
  LWGEOM **geoms;
1369
0
  uint32_t i, hascurve = 0;
1370
1371
0
  LWDEBUG(2, "lwmpoly_unstroke called.");
1372
1373
0
  geoms = lwalloc(sizeof(LWGEOM *)*mpoly->ngeoms);
1374
0
  for (i=0; i<mpoly->ngeoms; i++)
1375
0
  {
1376
0
    geoms[i] = lwpolygon_unstroke((LWPOLY *)mpoly->geoms[i]);
1377
0
    if (geoms[i]->type == CURVEPOLYTYPE)
1378
0
    {
1379
0
      hascurve = 1;
1380
0
    }
1381
0
  }
1382
0
  if (hascurve == 0)
1383
0
  {
1384
0
    for (i=0; i<mpoly->ngeoms; i++)
1385
0
    {
1386
0
      lwfree(geoms[i]); /* TODO: should this be lwgeom_free instead ? */
1387
0
    }
1388
0
    return lwgeom_clone_deep((LWGEOM *)mpoly);
1389
0
  }
1390
0
  return (LWGEOM *)lwcollection_construct(MULTISURFACETYPE, mpoly->srid, NULL, mpoly->ngeoms, geoms);
1391
0
}
1392
1393
LWGEOM *
1394
lwcollection_unstroke(const LWCOLLECTION *c)
1395
0
{
1396
0
  LWCOLLECTION *ret = lwalloc(sizeof(LWCOLLECTION));
1397
0
  memcpy(ret, c, sizeof(LWCOLLECTION));
1398
1399
0
  if (c->ngeoms > 0)
1400
0
  {
1401
0
    uint32_t i;
1402
0
    ret->geoms = lwalloc(sizeof(LWGEOM *)*c->ngeoms);
1403
0
    for (i=0; i < c->ngeoms; i++)
1404
0
    {
1405
0
      ret->geoms[i] = lwgeom_unstroke(c->geoms[i]);
1406
0
    }
1407
0
    if (c->bbox)
1408
0
    {
1409
0
      ret->bbox = gbox_copy(c->bbox);
1410
0
    }
1411
0
  }
1412
0
  else
1413
0
  {
1414
0
    ret->bbox = NULL;
1415
0
    ret->geoms = NULL;
1416
0
  }
1417
0
  return (LWGEOM *)ret;
1418
0
}
1419
1420
1421
LWGEOM *
1422
lwgeom_unstroke(const LWGEOM *geom)
1423
0
{
1424
0
  LWDEBUG(2, "lwgeom_unstroke called.");
1425
1426
0
  switch (geom->type)
1427
0
  {
1428
0
  case LINETYPE:
1429
0
    return lwline_unstroke((LWLINE *)geom);
1430
0
  case POLYGONTYPE:
1431
0
    return lwpolygon_unstroke((LWPOLY *)geom);
1432
0
  case MULTILINETYPE:
1433
0
    return lwmline_unstroke((LWMLINE *)geom);
1434
0
  case MULTIPOLYGONTYPE:
1435
0
    return lwmpolygon_unstroke((LWMPOLY *)geom);
1436
0
  case COLLECTIONTYPE:
1437
0
    return lwcollection_unstroke((LWCOLLECTION *)geom);
1438
0
  default:
1439
0
    return lwgeom_clone_deep(geom);
1440
0
  }
1441
0
}