Coverage Report

Created: 2026-07-16 06:51

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/postgis/liblwgeom/lwcompound.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
 *
23
 **********************************************************************/
24
25
26
#include <stdio.h>
27
#include <stdlib.h>
28
#include <string.h>
29
#include "liblwgeom_internal.h"
30
#include "lwgeom_log.h"
31
32
33
uint32_t
34
lwcompound_num_curves(const LWCOMPOUND *compound)
35
0
{
36
0
  if ( compound->type != COMPOUNDTYPE )
37
0
    lwerror("%s only supports compound curves", __func__);
38
0
  return compound->ngeoms;
39
0
}
40
41
const LWGEOM *
42
lwcollection_getsubcurve(const LWCOMPOUND *compound, uint32_t curvenum)
43
0
{
44
0
  return (const LWGEOM *)compound->geoms[curvenum];
45
0
}
46
47
static int
48
lwcompound_curve_endpoint(const LWGEOM *geom, POINT4D *pt)
49
0
{
50
0
  if (!geom || lwgeom_is_empty(geom))
51
0
    return LW_FAILURE;
52
53
0
  if (geom->type == NURBSCURVETYPE)
54
0
  {
55
0
    LWPOINT *endpoint = lwnurbscurve_evaluate((const LWNURBSCURVE*)geom, 1.0);
56
0
    int rv = LW_FAILURE;
57
0
    if (endpoint && endpoint->point && endpoint->point->npoints > 0)
58
0
      rv = getPoint4d_p(endpoint->point, 0, pt) ? LW_SUCCESS : LW_FAILURE;
59
0
    lwpoint_free(endpoint);
60
0
    return rv;
61
0
  }
62
63
0
  if (geom->type == LINETYPE || geom->type == CIRCSTRINGTYPE)
64
0
  {
65
0
    const LWLINE *line = (const LWLINE *)geom;
66
0
    if (!line->points || line->points->npoints < 1)
67
0
      return LW_FAILURE;
68
0
    return getPoint4d_p(line->points, line->points->npoints - 1, pt) ? LW_SUCCESS : LW_FAILURE;
69
0
  }
70
71
0
  return LW_FAILURE;
72
0
}
73
74
static int
75
lwcompound_points_equal(const POINT4D *a, const POINT4D *b, int hasz)
76
0
{
77
0
  if (!FP_EQUALS(a->x, b->x) || !FP_EQUALS(a->y, b->y))
78
0
    return LW_FALSE;
79
80
0
  if (hasz && !FP_EQUALS(a->z, b->z))
81
0
    return LW_FALSE;
82
83
0
  return LW_TRUE;
84
0
}
85
86
int
87
lwcompound_is_closed(const LWCOMPOUND *compound)
88
0
{
89
0
  POINT4D pt_start, pt_end;
90
91
0
  int hasz = lwgeom_has_z(lwcompound_as_lwgeom(compound));
92
0
  if (lwgeom_is_empty(lwcompound_as_lwgeom(compound)))
93
0
    return LW_FALSE;
94
95
  /* Single entry, closes on itself */
96
0
  if (compound->ngeoms == 1 && lwgeom_is_closed(compound->geoms[0]))
97
0
      return LW_TRUE;
98
99
  /* If internal connectivity is lacking, so is closure */
100
0
  if (!lwcompound_is_valid(compound))
101
0
    return LW_FALSE;
102
103
0
  if (lwgeom_startpoint(compound->geoms[0], &pt_start) == LW_FAILURE ||
104
0
      lwcompound_curve_endpoint(compound->geoms[compound->ngeoms-1], &pt_end) == LW_FAILURE)
105
0
    return LW_FALSE;
106
107
0
  return lwcompound_points_equal(&pt_start, &pt_end, hasz);
108
0
}
109
110
int
111
lwcompound_is_valid(const LWCOMPOUND *compound)
112
0
{
113
0
  int hasz = lwgeom_has_z(lwcompound_as_lwgeom(compound));
114
0
  if (lwgeom_is_empty(lwcompound_as_lwgeom(compound)))
115
0
    return LW_TRUE;
116
117
  /* Only one component, do not need to test connectivity */
118
0
  if (compound->ngeoms == 1)
119
0
    return LW_TRUE;
120
121
  /* Check internal connectivity between components */
122
0
  for (uint32_t i = 1; i < compound->ngeoms; i++)
123
0
  {
124
0
    POINT4D pt_start, pt_end;
125
0
    const LWGEOM *geom_start = compound->geoms[i];
126
0
    const LWGEOM *geom_end = compound->geoms[i-1];
127
128
    /* Empty cannot be a compound component, because it joins nothing */
129
0
    if (lwgeom_is_empty(geom_start) || lwgeom_is_empty(geom_end))
130
0
      return LW_FALSE;
131
132
0
    if (lwgeom_startpoint(geom_start, &pt_start) == LW_FAILURE ||
133
0
        lwcompound_curve_endpoint(geom_end, &pt_end) == LW_FAILURE)
134
0
      return LW_FALSE;
135
136
0
    if (!lwcompound_points_equal(&pt_start, &pt_end, hasz))
137
0
      return LW_FALSE;
138
0
  }
139
140
0
  return LW_TRUE;
141
0
}
142
143
double lwcompound_length(const LWCOMPOUND *comp)
144
0
{
145
0
  return lwcompound_length_2d(comp);
146
0
}
147
148
double lwcompound_length_2d(const LWCOMPOUND *comp)
149
0
{
150
0
  uint32_t i;
151
0
  double length = 0.0;
152
0
  if ( lwgeom_is_empty((LWGEOM*)comp) )
153
0
    return 0.0;
154
155
0
  for (i = 0; i < comp->ngeoms; i++)
156
0
  {
157
0
    length += lwgeom_length_2d(comp->geoms[i]);
158
0
  }
159
0
  return length;
160
0
}
161
162
int lwcompound_add_lwgeom(LWCOMPOUND *comp, LWGEOM *geom)
163
0
{
164
0
  LWCOLLECTION *col = (LWCOLLECTION*)comp;
165
166
  /* Empty things can't continuously join up with other things */
167
0
  if ( lwgeom_is_empty(geom) )
168
0
  {
169
0
    LWDEBUG(4, "Got an empty component for a compound curve!");
170
0
    return LW_FAILURE;
171
0
  }
172
173
0
  if( col->ngeoms > 0 )
174
0
  {
175
0
    POINT4D last, first;
176
0
    if (lwgeom_startpoint(geom, &first) == LW_FAILURE ||
177
0
        lwcompound_curve_endpoint(col->geoms[col->ngeoms - 1], &last) == LW_FAILURE)
178
0
      return LW_FAILURE;
179
180
0
    if ( !(FP_EQUALS(first.x,last.x) && FP_EQUALS(first.y,last.y)) )
181
0
    {
182
0
      LWDEBUG(4, "Components don't join up end-to-end!");
183
0
      LWDEBUGF(4, "first pt (%g %g %g %g) last pt (%g %g %g %g)", first.x, first.y, first.z, first.m, last.x, last.y, last.z, last.m);
184
0
      return LW_FAILURE;
185
0
    }
186
0
  }
187
188
0
  col = lwcollection_add_lwgeom(col, geom);
189
0
  return LW_SUCCESS;
190
0
}
191
192
LWCOMPOUND *
193
lwcompound_construct_empty(int32_t srid, char hasz, char hasm)
194
0
{
195
0
  LWCOMPOUND *ret = (LWCOMPOUND*)lwcollection_construct_empty(COMPOUNDTYPE, srid, hasz, hasm);
196
0
  return ret;
197
0
}
198
199
int lwgeom_contains_point(const LWGEOM *geom, const POINT2D *pt)
200
0
{
201
0
  switch( geom->type )
202
0
  {
203
0
    case LINETYPE:
204
0
      return ptarray_contains_point(((LWLINE*)geom)->points, pt);
205
0
    case CIRCSTRINGTYPE:
206
0
      return ptarrayarc_contains_point(((LWCIRCSTRING*)geom)->points, pt);
207
0
    case COMPOUNDTYPE:
208
0
      return lwcompound_contains_point((LWCOMPOUND*)geom, pt);
209
0
    case NURBSCURVETYPE:
210
0
    {
211
0
      int result;
212
0
      LWLINE *lwline = lwnurbscurve_to_linestring((LWNURBSCURVE*)geom, 32);
213
0
      result = ptarray_contains_point(lwline->points, pt);
214
0
      lwline_free(lwline);
215
0
      return result;
216
0
    }
217
0
  }
218
0
  lwerror("lwgeom_contains_point failed");
219
0
  return LW_FAILURE;
220
0
}
221
222
/*
223
 * Use a ray-casting count to determine if the point
224
 * is inside or outside of the compound curve. Ray-casting
225
 * is run against each component of the overall arc, and
226
 * the even/odd test run against the total of all components.
227
 * Returns LW_INSIDE / LW_BOUNDARY / LW_OUTSIDE
228
 */
229
int
230
lwcompound_contains_point(const LWCOMPOUND *comp, const POINT2D *pt)
231
0
{
232
0
  int intersections = 0;
233
234
0
  if (lwgeom_is_empty(lwcompound_as_lwgeom(comp)))
235
0
    return LW_OUTSIDE;
236
237
0
  for (uint32_t j = 0; j < comp->ngeoms; j++)
238
0
  {
239
0
    int on_boundary = LW_FALSE;
240
0
    const LWGEOM *sub = comp->geoms[j];
241
0
    if (sub->type == LINETYPE)
242
0
    {
243
0
      LWLINE *lwline = lwgeom_as_lwline(sub);
244
0
      intersections += ptarray_raycast_intersections(lwline->points, pt, &on_boundary);
245
0
    }
246
0
    else if (sub->type == CIRCSTRINGTYPE)
247
0
    {
248
0
      LWCIRCSTRING *lwcirc = lwgeom_as_lwcircstring(sub);
249
0
      intersections += ptarrayarc_raycast_intersections(lwcirc->points, pt, &on_boundary);
250
0
    }
251
0
    else if (sub->type == NURBSCURVETYPE)
252
0
    {
253
0
      LWLINE *lwline = lwnurbscurve_to_linestring((LWNURBSCURVE*)sub, 32);
254
0
      intersections += ptarray_raycast_intersections(lwline->points, pt, &on_boundary);
255
0
      lwline_free(lwline);
256
0
    }
257
0
    else
258
0
    {
259
0
      lwerror("%s: unsupported type %s", __func__, lwtype_name(sub->type));
260
0
    }
261
0
    if (on_boundary)
262
0
      return LW_BOUNDARY;
263
0
  }
264
265
  /*
266
   * Odd number of intersections means inside.
267
   * Even means outside.
268
   */
269
0
  return (intersections % 2) ? LW_INSIDE : LW_OUTSIDE;
270
0
}
271
272
LWCOMPOUND *
273
lwcompound_construct_from_lwline(const LWLINE *lwline)
274
0
{
275
0
  LWCOMPOUND* ogeom = lwcompound_construct_empty(lwline->srid, FLAGS_GET_Z(lwline->flags), FLAGS_GET_M(lwline->flags));
276
0
  lwcompound_add_lwgeom(ogeom, lwgeom_clone((LWGEOM*)lwline));
277
  /* ogeom->bbox = lwline->bbox; */
278
0
  return ogeom;
279
0
}
280
281
LWPOINT*
282
lwcompound_get_lwpoint(const LWCOMPOUND *lwcmp, uint32_t where)
283
0
{
284
0
  uint32_t i;
285
0
  uint32_t count = 0;
286
0
  uint32_t npoints = 0;
287
0
  if ( lwgeom_is_empty((LWGEOM*)lwcmp) )
288
0
    return NULL;
289
290
0
  npoints = lwgeom_count_vertices((LWGEOM*)lwcmp);
291
0
  if ( where >= npoints )
292
0
  {
293
0
    lwerror("%s: index %d is not in range of number of vertices (%d) in input", __func__, where, npoints);
294
0
    return NULL;
295
0
  }
296
297
0
  for ( i = 0; i < lwcmp->ngeoms; i++ )
298
0
  {
299
0
    LWGEOM* part = lwcmp->geoms[i];
300
0
    uint32_t npoints_part = lwgeom_count_vertices(part);
301
0
    if ( where >= count && where < count + npoints_part )
302
0
    {
303
0
      if (part->type == NURBSCURVETYPE)
304
0
      {
305
0
        const LWNURBSCURVE *curve = (const LWNURBSCURVE*)part;
306
0
        POINTARRAY *pa;
307
0
        POINT4D pt;
308
309
0
        if (!curve->points || where - count >= curve->points->npoints)
310
0
          return NULL;
311
312
0
        pa = ptarray_construct_empty(FLAGS_GET_Z(curve->flags), FLAGS_GET_M(curve->flags), 1);
313
0
        pt = getPoint4d(curve->points, where - count);
314
0
        ptarray_append_point(pa, &pt, LW_TRUE);
315
0
        return lwpoint_construct(curve->srid, NULL, pa);
316
0
      }
317
0
      return lwline_get_lwpoint((LWLINE*)part, where - count);
318
0
    }
319
0
    else
320
0
    {
321
0
      count += npoints_part;
322
0
    }
323
0
  }
324
325
0
  return NULL;
326
0
}
327
328
329
330
LWPOINT *
331
lwcompound_get_startpoint(const LWCOMPOUND *lwcmp)
332
0
{
333
0
  if ( lwcmp->ngeoms < 1 )
334
0
    return NULL;
335
336
0
  if ( lwcmp->geoms[0]->type == NURBSCURVETYPE )
337
0
    return lwnurbscurve_evaluate((const LWNURBSCURVE*)lwcmp->geoms[0], 0.0);
338
339
0
  return lwcompound_get_lwpoint(lwcmp, 0);
340
0
}
341
342
LWPOINT *
343
lwcompound_get_endpoint(const LWCOMPOUND *lwcmp)
344
0
{
345
0
  LWGEOM *last;
346
0
  LWLINE *lwline;
347
0
  if ( lwcmp->ngeoms < 1 )
348
0
  {
349
0
    return NULL;
350
0
  }
351
352
0
  last = lwcmp->geoms[lwcmp->ngeoms-1];
353
0
  if ( last->type == NURBSCURVETYPE )
354
0
    return lwnurbscurve_evaluate((const LWNURBSCURVE*)last, 1.0);
355
356
0
  lwline = (LWLINE*)last;
357
358
0
  if ( (!lwline) || (!lwline->points) || (lwline->points->npoints < 1) )
359
0
  {
360
0
    return NULL;
361
0
  }
362
363
0
  return lwline_get_lwpoint(lwline, lwline->points->npoints-1);
364
0
}