Coverage Report

Created: 2026-03-31 06:40

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