Coverage Report

Created: 2026-08-14 06:58

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/postgis/liblwgeom/lwpoly.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) 2012 Sandro Santilli <strk@kbt.io>
22
 * Copyright (C) 2001-2006 Refractions Research Inc.
23
 * Copyright (C) 2026 Darafei Praliaskouski <me@komzpa.net>
24
 *
25
 **********************************************************************/
26
27
/* basic LWPOLY manipulation */
28
29
#include <stdio.h>
30
#include <stdlib.h>
31
#include <string.h>
32
#include <math.h>
33
#include "liblwgeom_internal.h"
34
#include "lwgeom_log.h"
35
36
37
#define CHECK_POLY_RINGS_ZM 1
38
39
/* construct a new LWPOLY.  arrays (points/points per ring) will NOT be copied
40
 * use SRID=SRID_UNKNOWN for unknown SRID (will have 8bit type's S = 0)
41
 */
42
LWPOLY *
43
lwpoly_construct(int32_t srid, GBOX *bbox, uint32_t nrings, POINTARRAY **points)
44
0
{
45
0
  LWPOLY *result;
46
0
  int hasz, hasm;
47
0
#ifdef CHECK_POLY_RINGS_ZM
48
0
  char zm;
49
0
  uint32_t i;
50
0
#endif
51
52
0
  if ( nrings < 1 ) lwerror("lwpoly_construct: need at least 1 ring");
53
54
0
  hasz = FLAGS_GET_Z(points[0]->flags);
55
0
  hasm = FLAGS_GET_M(points[0]->flags);
56
57
0
#ifdef CHECK_POLY_RINGS_ZM
58
0
  zm = FLAGS_GET_ZM(points[0]->flags);
59
0
  for (i=1; i<nrings; i++)
60
0
  {
61
0
    if ( zm != FLAGS_GET_ZM(points[i]->flags) )
62
0
      lwerror("lwpoly_construct: mixed dimensioned rings");
63
0
  }
64
0
#endif
65
66
0
  result = (LWPOLY*) lwalloc(sizeof(LWPOLY));
67
0
  result->type = POLYGONTYPE;
68
0
  result->flags = lwflags(hasz, hasm, 0);
69
0
  FLAGS_SET_BBOX(result->flags, bbox?1:0);
70
0
  result->srid = srid;
71
0
  result->nrings = nrings;
72
0
  result->maxrings = nrings;
73
0
  result->rings = points;
74
0
  result->bbox = bbox;
75
76
0
  return result;
77
0
}
78
79
LWPOLY*
80
lwpoly_construct_rectangle(char hasz, char hasm, POINT4D *p1, POINT4D *p2,
81
    POINT4D *p3, POINT4D *p4)
82
0
{
83
0
  POINTARRAY *pa = ptarray_construct_empty(hasz, hasm, 5);
84
0
  LWPOLY *lwpoly = lwpoly_construct_empty(SRID_UNKNOWN, hasz, hasm);
85
86
0
  ptarray_append_point(pa, p1, LW_TRUE);
87
0
  ptarray_append_point(pa, p2, LW_TRUE);
88
0
  ptarray_append_point(pa, p3, LW_TRUE);
89
0
  ptarray_append_point(pa, p4, LW_TRUE);
90
0
  ptarray_append_point(pa, p1, LW_TRUE);
91
92
0
  lwpoly_add_ring(lwpoly, pa);
93
94
0
  return lwpoly;
95
0
}
96
97
LWPOLY *
98
lwpoly_construct_envelope(int32_t srid, double x1, double y1, double x2, double y2)
99
0
{
100
0
  POINT4D p1, p2, p3, p4;
101
0
  LWPOLY *poly;
102
103
0
  p1.x = x1;
104
0
  p1.y = y1;
105
0
  p2.x = x1;
106
0
  p2.y = y2;
107
0
  p3.x = x2;
108
0
  p3.y = y2;
109
0
  p4.x = x2;
110
0
  p4.y = y1;
111
112
0
  poly = lwpoly_construct_rectangle(0, 0, &p1, &p2, &p3, &p4);
113
0
  lwgeom_set_srid(lwpoly_as_lwgeom(poly), srid);
114
0
  lwgeom_add_bbox(lwpoly_as_lwgeom(poly));
115
116
0
  return poly;
117
0
}
118
119
LWPOLY *
120
lwpoly_construct_circle(int32_t srid, double x, double y, double radius, uint32_t segments_per_quarter, char exterior)
121
0
{
122
0
  const uint32_t segments = 4*segments_per_quarter;
123
0
  double theta;
124
0
  LWPOLY *lwpoly;
125
0
  POINTARRAY *pa;
126
0
  POINT4D pt;
127
0
  uint32_t i;
128
129
0
  if (segments_per_quarter == 0)
130
0
  {
131
0
    lwerror("Need at least one segment per quarter-circle.");
132
0
    return NULL;
133
0
  }
134
135
0
  if (radius < 0)
136
0
  {
137
0
    lwerror("Radius must be positive.");
138
0
    return NULL;
139
0
  }
140
141
0
  theta = 2*M_PI / segments;
142
143
0
  lwpoly = lwpoly_construct_empty(srid, LW_FALSE, LW_FALSE);
144
0
  pa = ptarray_construct_empty(LW_FALSE, LW_FALSE, segments + 1);
145
146
0
  if (exterior)
147
0
    radius *= sqrt(1 + pow(tan(theta/2), 2));
148
149
0
  for (i = 0; i < segments; i++)
150
0
  {
151
0
    pt.x = x + radius*sin(i * theta);
152
0
    pt.y = y + radius*cos(i * theta);
153
0
    ptarray_append_point(pa, &pt, LW_TRUE);
154
0
  }
155
0
  getPoint4d_p(pa, 0, &pt);
156
0
  ptarray_append_point(pa, &pt, LW_TRUE);
157
158
0
  lwpoly_add_ring(lwpoly, pa);
159
0
  return lwpoly;
160
0
}
161
162
LWPOLY *
163
lwpoly_construct_empty(int32_t srid, char hasz, char hasm)
164
38.5k
{
165
38.5k
  LWPOLY *result = lwalloc(sizeof(LWPOLY));
166
38.5k
  result->type = POLYGONTYPE;
167
38.5k
  result->flags = lwflags(hasz,hasm,0);
168
38.5k
  result->srid = srid;
169
38.5k
  result->nrings = 0;
170
38.5k
  result->maxrings = 1; /* Allocate room for ring, just in case. */
171
38.5k
  result->rings = lwalloc(result->maxrings * sizeof(POINTARRAY*));
172
38.5k
  result->bbox = NULL;
173
38.5k
  return result;
174
38.5k
}
175
176
void
177
lwpoly_free(LWPOLY* poly)
178
38.5k
{
179
38.5k
  uint32_t t;
180
181
38.5k
  if (!poly) return;
182
183
38.5k
  if (poly->bbox) lwfree(poly->bbox);
184
185
38.5k
  if ( poly->rings )
186
38.5k
  {
187
175k
    for (t = 0; t < poly->nrings; t++)
188
137k
      if (poly->rings[t]) ptarray_free(poly->rings[t]);
189
38.5k
    lwfree(poly->rings);
190
38.5k
  }
191
192
38.5k
  lwfree(poly);
193
38.5k
}
194
195
void printLWPOLY(LWPOLY *poly)
196
0
{
197
0
  uint32_t t;
198
0
  lwnotice("LWPOLY {");
199
0
  lwnotice("    ndims = %i", (int)FLAGS_NDIMS(poly->flags));
200
0
  lwnotice("    SRID = %i", (int)poly->srid);
201
0
  lwnotice("    nrings = %i", (int)poly->nrings);
202
0
  for (t=0; t<poly->nrings; t++)
203
0
  {
204
0
    lwnotice("    RING # %i :",t);
205
0
    printPA(poly->rings[t]);
206
0
  }
207
0
  lwnotice("}");
208
0
}
209
210
/* @brief Clone LWLINE object. Serialized point lists are not copied.
211
 *
212
 * @see ptarray_clone
213
 */
214
LWPOLY *
215
lwpoly_clone(const LWPOLY *g)
216
0
{
217
0
  uint32_t i;
218
0
  LWPOLY *ret = lwalloc(sizeof(LWPOLY));
219
0
  memcpy(ret, g, sizeof(LWPOLY));
220
0
  ret->rings = lwalloc(sizeof(POINTARRAY *)*g->nrings);
221
0
  for ( i = 0; i < g->nrings; i++ ) {
222
0
    ret->rings[i] = ptarray_clone(g->rings[i]);
223
0
  }
224
0
  if ( g->bbox ) ret->bbox = gbox_copy(g->bbox);
225
0
  return ret;
226
0
}
227
228
/* Deep clone LWPOLY object. POINTARRAY are copied, as is ring array */
229
LWPOLY *
230
lwpoly_clone_deep(const LWPOLY *g)
231
0
{
232
0
  uint32_t i;
233
0
  LWPOLY *ret = lwalloc(sizeof(LWPOLY));
234
0
  memcpy(ret, g, sizeof(LWPOLY));
235
0
  if ( g->bbox ) ret->bbox = gbox_copy(g->bbox);
236
0
  ret->rings = lwalloc(sizeof(POINTARRAY *)*g->nrings);
237
0
  for ( i = 0; i < ret->nrings; i++ )
238
0
  {
239
0
    ret->rings[i] = ptarray_clone_deep(g->rings[i]);
240
0
  }
241
0
  FLAGS_SET_READONLY(ret->flags,0);
242
0
  return ret;
243
0
}
244
245
/**
246
* Add a ring to a polygon. Point array will be referenced, not copied.
247
*/
248
int
249
lwpoly_add_ring(LWPOLY *poly, POINTARRAY *pa)
250
137k
{
251
137k
  if( ! poly || ! pa )
252
0
    return LW_FAILURE;
253
254
  /* We have used up our storage, add some more. */
255
137k
  if( poly->nrings >= poly->maxrings )
256
734
  {
257
734
    int new_maxrings = 2 * (poly->nrings + 1);
258
734
    poly->rings = lwrealloc(poly->rings, new_maxrings * sizeof(POINTARRAY*));
259
734
    poly->maxrings = new_maxrings;
260
734
  }
261
262
  /* Add the new ring entry. */
263
137k
  poly->rings[poly->nrings] = pa;
264
137k
  poly->nrings++;
265
266
137k
  return LW_SUCCESS;
267
137k
}
268
269
void
270
lwpoly_force_orientation(LWPOLY *poly, int orientation)
271
0
{
272
  /* No-op no orientation */
273
0
  if (orientation == LW_NONE) return;
274
275
  /* No-op empties */
276
0
  if (lwpoly_is_empty(poly)) return;
277
278
  /* External ring */
279
0
  if (!ptarray_has_orientation(poly->rings[0], orientation))
280
0
    ptarray_reverse_in_place(poly->rings[0]);
281
282
  /* Internal rings must run opposite to external */
283
0
  for (uint32_t i = 1; i < poly->nrings; i++)
284
0
    if (!ptarray_has_orientation(poly->rings[i], -1 * orientation))
285
0
      ptarray_reverse_in_place(poly->rings[i]);
286
0
}
287
288
int
289
lwpoly_has_orientation(const LWPOLY *poly, int orientation)
290
0
{
291
0
  if (lwpoly_is_empty(poly))
292
0
    return LW_TRUE;
293
294
  /* Exterior ring matches orientation? */
295
0
  if(!ptarray_has_orientation(poly->rings[0], orientation))
296
0
    return LW_FALSE;
297
298
  /* Interior rings are opposite of orientation? */
299
0
  for (uint32_t i = 1; i < poly->nrings; i++)
300
0
    if (!ptarray_has_orientation(poly->rings[i], -1 * orientation))
301
0
      return LW_FALSE;
302
303
0
  return LW_TRUE;
304
0
}
305
306
void
307
lwpoly_release(LWPOLY *lwpoly)
308
0
{
309
0
  lwgeom_release(lwpoly_as_lwgeom(lwpoly));
310
0
}
311
312
LWPOLY *
313
lwpoly_segmentize2d(const LWPOLY *poly, double dist)
314
0
{
315
0
  POINTARRAY **newrings;
316
0
  uint32_t i;
317
318
0
  newrings = lwalloc(sizeof(POINTARRAY *)*poly->nrings);
319
0
  for (i=0; i<poly->nrings; i++)
320
0
  {
321
0
    newrings[i] = ptarray_segmentize2d(poly->rings[i], dist);
322
0
    if ( ! newrings[i] )
323
0
    {
324
0
      uint32_t j = 0;
325
0
      for (j = 0; j < i; j++)
326
0
        ptarray_free(newrings[j]);
327
0
      lwfree(newrings);
328
0
      return NULL;
329
0
    }
330
0
  }
331
0
  return lwpoly_construct(poly->srid, NULL,
332
0
                          poly->nrings, newrings);
333
0
}
334
335
/*
336
 * check coordinate equality
337
 * ring and coordinate order is considered
338
 */
339
char
340
lwpoly_same(const LWPOLY *p1, const LWPOLY *p2)
341
0
{
342
0
  uint32_t i;
343
344
0
  if ( p1->nrings != p2->nrings ) return 0;
345
0
  for (i=0; i<p1->nrings; i++)
346
0
  {
347
0
    if ( ! ptarray_same(p1->rings[i], p2->rings[i]) )
348
0
      return 0;
349
0
  }
350
0
  return 1;
351
0
}
352
353
/*
354
 * Construct a polygon from a LWLINE being
355
 * the shell and an array of LWLINE (possibly NULL) being holes.
356
 * Pointarrays from input geoms are cloned.
357
 * SRID must be the same for each input line.
358
 * Input lines must have at least 4 points, and be closed.
359
 */
360
LWPOLY *
361
lwpoly_from_lwlines(const LWLINE *shell,
362
                    uint32_t nholes, const LWLINE **holes)
363
0
{
364
0
  uint32_t nrings;
365
0
  POINTARRAY **rings = lwalloc((nholes+1)*sizeof(POINTARRAY *));
366
0
  int32_t srid = shell->srid;
367
0
  LWPOLY *ret;
368
369
0
  if ( shell->points->npoints < 4 )
370
0
    lwerror("lwpoly_from_lwlines: shell must have at least 4 points");
371
0
  if ( ! ptarray_is_closed_2d(shell->points) )
372
0
    lwerror("lwpoly_from_lwlines: shell must be closed");
373
0
  rings[0] = ptarray_clone_deep(shell->points);
374
375
0
  for (nrings=1; nrings<=nholes; nrings++)
376
0
  {
377
0
    const LWLINE *hole = holes[nrings-1];
378
379
0
    if ( hole->srid != srid )
380
0
      lwerror("lwpoly_from_lwlines: mixed SRIDs in input lines");
381
382
0
    if ( hole->points->npoints < 4 )
383
0
      lwerror("lwpoly_from_lwlines: holes must have at least 4 points");
384
0
    if ( ! ptarray_is_closed_2d(hole->points) )
385
0
      lwerror("lwpoly_from_lwlines: holes must be closed");
386
387
0
    rings[nrings] = ptarray_clone_deep(hole->points);
388
0
  }
389
390
0
  ret = lwpoly_construct(srid, NULL, nrings, rings);
391
0
  return ret;
392
0
}
393
394
LWPOLY*
395
lwpoly_force_dims(const LWPOLY *poly, int hasz, int hasm, double zval, double mval)
396
0
{
397
0
  LWPOLY *polyout;
398
399
  /* Return 2D empty */
400
0
  if( lwpoly_is_empty(poly) )
401
0
  {
402
0
    polyout = lwpoly_construct_empty(poly->srid, hasz, hasm);
403
0
  }
404
0
  else
405
0
  {
406
0
    POINTARRAY **rings = NULL;
407
0
    uint32_t i;
408
0
    rings = lwalloc(sizeof(POINTARRAY*) * poly->nrings);
409
0
    for( i = 0; i < poly->nrings; i++ )
410
0
    {
411
0
      rings[i] = ptarray_force_dims(poly->rings[i], hasz, hasm, zval, mval);
412
0
    }
413
0
    polyout = lwpoly_construct(poly->srid, NULL, poly->nrings, rings);
414
0
  }
415
0
  polyout->type = poly->type;
416
0
  return polyout;
417
0
}
418
419
uint32_t lwpoly_count_vertices(const LWPOLY *poly)
420
0
{
421
0
  uint32_t i = 0;
422
0
  uint32_t v = 0; /* vertices */
423
0
  assert(poly);
424
0
  for ( i = 0; i < poly->nrings; i ++ )
425
0
  {
426
0
    v += poly->rings[i]->npoints;
427
0
  }
428
0
  return v;
429
0
}
430
431
/**
432
* Find the area of the outer ring - sum (area of inner rings).
433
*/
434
double
435
lwpoly_area(const LWPOLY *poly)
436
0
{
437
0
  double poly_area = 0.0;
438
0
  uint32_t i;
439
440
0
  if ( ! poly )
441
0
    lwerror("lwpoly_area called with null polygon pointer!");
442
443
0
  for ( i=0; i < poly->nrings; i++ )
444
0
  {
445
0
    POINTARRAY *ring = poly->rings[i];
446
0
    double ringarea = 0.0;
447
448
    /* Empty or messed-up ring. */
449
0
    if ( ring->npoints < 3 )
450
0
      continue;
451
452
0
    ringarea = fabs(ptarray_signed_area(ring));
453
0
    if ( i == 0 ) /* Outer ring, positive area! */
454
0
      poly_area += ringarea;
455
0
    else /* Inner ring, negative area! */
456
0
      poly_area -= ringarea;
457
0
  }
458
459
0
  return poly_area;
460
0
}
461
462
463
/**
464
 * Compute the sum of polygon rings length.
465
 * Could use a more numerically stable calculator...
466
 */
467
double
468
lwpoly_perimeter(const LWPOLY *poly)
469
0
{
470
0
  double result=0.0;
471
0
  uint32_t i;
472
473
0
  LWDEBUGF(2, "in lwgeom_polygon_perimeter (%d rings)", poly->nrings);
474
475
0
  for (i=0; i<poly->nrings; i++)
476
0
    result += ptarray_length(poly->rings[i]);
477
478
0
  return result;
479
0
}
480
481
/**
482
 * Compute the sum of polygon rings length (forcing 2d computation).
483
 * Could use a more numerically stable calculator...
484
 */
485
double
486
lwpoly_perimeter_2d(const LWPOLY *poly)
487
0
{
488
0
  double result=0.0;
489
0
  uint32_t i;
490
491
0
  LWDEBUGF(2, "in lwgeom_polygon_perimeter (%d rings)", poly->nrings);
492
493
0
  for (i=0; i<poly->nrings; i++)
494
0
    result += ptarray_length_2d(poly->rings[i]);
495
496
0
  return result;
497
0
}
498
499
int
500
lwpoly_is_closed(const LWPOLY *poly)
501
0
{
502
0
  uint32_t i = 0;
503
504
0
  if ( poly->nrings == 0 )
505
0
    return LW_TRUE;
506
507
0
  for ( i = 0; i < poly->nrings; i++ )
508
0
  {
509
0
    if (FLAGS_GET_Z(poly->flags))
510
0
    {
511
0
      if ( ! ptarray_is_closed_3d(poly->rings[i]) )
512
0
        return LW_FALSE;
513
0
    }
514
0
    else
515
0
    {
516
0
      if ( ! ptarray_is_closed_2d(poly->rings[i]) )
517
0
        return LW_FALSE;
518
0
    }
519
0
  }
520
521
0
  return LW_TRUE;
522
0
}
523
524
int
525
lwpoly_startpoint(const LWPOLY* poly, POINT4D* pt)
526
0
{
527
0
  if ( poly->nrings < 1 )
528
0
    return LW_FAILURE;
529
0
  return ptarray_startpoint(poly->rings[0], pt);
530
0
}
531
532
int
533
lwpoly_contains_point(const LWPOLY *poly, const POINT2D *pt)
534
0
{
535
0
  uint32_t i;
536
0
  int t;
537
538
0
  if ( lwpoly_is_empty(poly) )
539
0
    return LW_OUTSIDE;
540
541
0
  t = ptarray_contains_point(poly->rings[0], pt);
542
543
0
  if (t == LW_INSIDE)
544
0
  {
545
0
    for (i = 1; i < poly->nrings; i++)
546
0
    {
547
0
      t = ptarray_contains_point(poly->rings[i], pt);
548
0
      if (t == LW_INSIDE)
549
0
        return LW_OUTSIDE;
550
0
      if (t == LW_BOUNDARY)
551
0
      {
552
0
        return LW_BOUNDARY;
553
0
      }
554
0
    }
555
0
    return LW_INSIDE;
556
0
  }
557
0
  else
558
0
    return t;
559
0
}