Coverage Report

Created: 2026-08-31 06:50

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/postgis/liblwgeom/gbox.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 2009 Paul Ramsey <pramsey@cleverelephant.ca>
22
 *
23
 **********************************************************************/
24
25
#include "liblwgeom_internal.h"
26
#include "lwgeodetic.h"
27
#include "lwgeom_log.h"
28
#include <float.h>
29
#include <stdlib.h>
30
#include <math.h>
31
32
typedef struct {
33
  double x;
34
  double y;
35
  double z;
36
  double m;
37
  double w;
38
} NURBS_BBOX_HPOINT;
39
40
GBOX* gbox_new(lwflags_t flags)
41
0
{
42
0
  GBOX *g = (GBOX*)lwalloc(sizeof(GBOX));
43
0
  gbox_init(g);
44
0
  g->flags = flags;
45
0
  return g;
46
0
}
47
48
void gbox_init(GBOX *gbox)
49
0
{
50
0
  memset(gbox, 0, sizeof(GBOX));
51
0
}
52
53
GBOX* gbox_clone(const GBOX *gbox)
54
0
{
55
0
  GBOX *g = lwalloc(sizeof(GBOX));
56
0
  memcpy(g, gbox, sizeof(GBOX));
57
0
  return g;
58
0
}
59
60
/* TODO to be removed */
61
BOX3D* box3d_from_gbox(const GBOX *gbox)
62
0
{
63
0
  BOX3D *b;
64
0
  assert(gbox);
65
66
0
  b = lwalloc(sizeof(BOX3D));
67
68
0
  b->xmin = gbox->xmin;
69
0
  b->xmax = gbox->xmax;
70
0
  b->ymin = gbox->ymin;
71
0
  b->ymax = gbox->ymax;
72
73
0
  if ( FLAGS_GET_Z(gbox->flags) )
74
0
  {
75
0
    b->zmin = gbox->zmin;
76
0
    b->zmax = gbox->zmax;
77
0
  }
78
0
  else
79
0
  {
80
0
    b->zmin = b->zmax = 0.0;
81
0
  }
82
83
0
  b->srid = SRID_UNKNOWN;
84
0
  return b;
85
0
}
86
87
/* TODO to be removed */
88
GBOX* box3d_to_gbox(const BOX3D *b3d)
89
0
{
90
0
  GBOX *b;
91
0
  assert(b3d);
92
93
0
  b = lwalloc(sizeof(GBOX));
94
95
0
  b->xmin = b3d->xmin;
96
0
  b->xmax = b3d->xmax;
97
0
  b->ymin = b3d->ymin;
98
0
  b->ymax = b3d->ymax;
99
0
  b->zmin = b3d->zmin;
100
0
  b->zmax = b3d->zmax;
101
102
0
  return b;
103
0
}
104
105
void gbox_expand(GBOX *g, double d)
106
0
{
107
0
  g->xmin -= d;
108
0
  g->xmax += d;
109
0
  g->ymin -= d;
110
0
  g->ymax += d;
111
0
  if (FLAGS_GET_Z(g->flags) || FLAGS_GET_GEODETIC(g->flags))
112
0
  {
113
0
    g->zmin -= d;
114
0
    g->zmax += d;
115
0
  }
116
0
  if (FLAGS_GET_M(g->flags))
117
0
  {
118
0
    g->mmin -= d;
119
0
    g->mmax += d;
120
0
  }
121
0
}
122
123
void gbox_expand_xyzm(GBOX *g, double dx, double dy, double dz, double dm)
124
0
{
125
0
  g->xmin -= dx;
126
0
  g->xmax += dx;
127
0
  g->ymin -= dy;
128
0
  g->ymax += dy;
129
130
0
  if (FLAGS_GET_Z(g->flags))
131
0
  {
132
0
    g->zmin -= dz;
133
0
    g->zmax += dz;
134
0
  }
135
136
0
  if (FLAGS_GET_M(g->flags))
137
0
  {
138
0
    g->mmin -= dm;
139
0
    g->mmax += dm;
140
0
  }
141
0
}
142
143
int gbox_union(const GBOX *g1, const GBOX *g2, GBOX *gout)
144
0
{
145
0
  if ( ( ! g1 ) && ( ! g2 ) )
146
0
    return LW_FALSE;
147
0
  else if (!g1)
148
0
  {
149
0
    memcpy(gout, g2, sizeof(GBOX));
150
0
    return LW_TRUE;
151
0
  }
152
0
  else if (!g2)
153
0
  {
154
0
    memcpy(gout, g1, sizeof(GBOX));
155
0
    return LW_TRUE;
156
0
  }
157
158
0
  gout->flags = g1->flags;
159
160
0
  gout->xmin = FP_MIN(g1->xmin, g2->xmin);
161
0
  gout->xmax = FP_MAX(g1->xmax, g2->xmax);
162
163
0
  gout->ymin = FP_MIN(g1->ymin, g2->ymin);
164
0
  gout->ymax = FP_MAX(g1->ymax, g2->ymax);
165
166
0
  gout->zmin = FP_MIN(g1->zmin, g2->zmin);
167
0
  gout->zmax = FP_MAX(g1->zmax, g2->zmax);
168
169
0
  return LW_TRUE;
170
0
}
171
172
int gbox_same(const GBOX *g1, const GBOX *g2)
173
0
{
174
0
  if (FLAGS_GET_ZM(g1->flags) != FLAGS_GET_ZM(g2->flags))
175
0
    return LW_FALSE;
176
177
0
  if (!gbox_same_2d(g1, g2)) return LW_FALSE;
178
179
0
  if (FLAGS_GET_Z(g1->flags) && (g1->zmin != g2->zmin || g1->zmax != g2->zmax))
180
0
    return LW_FALSE;
181
0
  if (FLAGS_GET_M(g1->flags) && (g1->mmin != g2->mmin || g1->mmax != g2->mmax))
182
0
    return LW_FALSE;
183
184
0
  return LW_TRUE;
185
0
}
186
187
int gbox_same_2d(const GBOX *g1, const GBOX *g2)
188
0
{
189
0
    if (g1->xmin == g2->xmin && g1->ymin == g2->ymin &&
190
0
        g1->xmax == g2->xmax && g1->ymax == g2->ymax)
191
0
    return LW_TRUE;
192
0
  return LW_FALSE;
193
0
}
194
195
int gbox_same_2d_float(const GBOX *g1, const GBOX *g2)
196
0
{
197
0
  if  ((g1->xmax == g2->xmax || next_float_up(g1->xmax)   == next_float_up(g2->xmax))   &&
198
0
       (g1->ymax == g2->ymax || next_float_up(g1->ymax)   == next_float_up(g2->ymax))   &&
199
0
       (g1->xmin == g2->xmin || next_float_down(g1->xmin) == next_float_down(g1->xmin)) &&
200
0
       (g1->ymin == g2->ymin || next_float_down(g2->ymin) == next_float_down(g2->ymin)))
201
0
      return LW_TRUE;
202
0
  return LW_FALSE;
203
0
}
204
205
int gbox_is_valid(const GBOX *gbox)
206
0
{
207
  /* X */
208
0
  if ( ! isfinite(gbox->xmin) || isnan(gbox->xmin) ||
209
0
       ! isfinite(gbox->xmax) || isnan(gbox->xmax) )
210
0
    return LW_FALSE;
211
212
  /* Y */
213
0
  if ( ! isfinite(gbox->ymin) || isnan(gbox->ymin) ||
214
0
       ! isfinite(gbox->ymax) || isnan(gbox->ymax) )
215
0
    return LW_FALSE;
216
217
  /* Z */
218
0
  if ( FLAGS_GET_GEODETIC(gbox->flags) || FLAGS_GET_Z(gbox->flags) )
219
0
  {
220
0
    if ( ! isfinite(gbox->zmin) || isnan(gbox->zmin) ||
221
0
         ! isfinite(gbox->zmax) || isnan(gbox->zmax) )
222
0
      return LW_FALSE;
223
0
  }
224
225
  /* M */
226
0
  if ( FLAGS_GET_M(gbox->flags) )
227
0
  {
228
0
    if ( ! isfinite(gbox->mmin) || isnan(gbox->mmin) ||
229
0
         ! isfinite(gbox->mmax) || isnan(gbox->mmax) )
230
0
      return LW_FALSE;
231
0
  }
232
233
0
  return LW_TRUE;
234
0
}
235
236
int gbox_merge_point3d(const POINT3D *p, GBOX *gbox)
237
0
{
238
0
  if ( gbox->xmin > p->x ) gbox->xmin = p->x;
239
0
  if ( gbox->ymin > p->y ) gbox->ymin = p->y;
240
0
  if ( gbox->zmin > p->z ) gbox->zmin = p->z;
241
0
  if ( gbox->xmax < p->x ) gbox->xmax = p->x;
242
0
  if ( gbox->ymax < p->y ) gbox->ymax = p->y;
243
0
  if ( gbox->zmax < p->z ) gbox->zmax = p->z;
244
0
  return LW_SUCCESS;
245
0
}
246
247
int gbox_init_point3d(const POINT3D *p, GBOX *gbox)
248
0
{
249
0
  gbox->xmin = gbox->xmax = p->x;
250
0
  gbox->ymin = gbox->ymax = p->y;
251
0
  gbox->zmin = gbox->zmax = p->z;
252
0
  return LW_SUCCESS;
253
0
}
254
255
int gbox_contains_point3d(const GBOX *gbox, const POINT3D *pt)
256
0
{
257
0
  if ( gbox->xmin > pt->x || gbox->ymin > pt->y || gbox->zmin > pt->z ||
258
0
       gbox->xmax < pt->x || gbox->ymax < pt->y || gbox->zmax < pt->z )
259
0
  {
260
0
    return LW_FALSE;
261
0
  }
262
0
  return LW_TRUE;
263
0
}
264
265
int gbox_merge(const GBOX *new_box, GBOX *merge_box)
266
0
{
267
0
  assert(merge_box);
268
269
0
  if ( FLAGS_GET_ZM(merge_box->flags) != FLAGS_GET_ZM(new_box->flags) )
270
0
    return LW_FAILURE;
271
272
0
  if ( new_box->xmin < merge_box->xmin) merge_box->xmin = new_box->xmin;
273
0
  if ( new_box->ymin < merge_box->ymin) merge_box->ymin = new_box->ymin;
274
0
  if ( new_box->xmax > merge_box->xmax) merge_box->xmax = new_box->xmax;
275
0
  if ( new_box->ymax > merge_box->ymax) merge_box->ymax = new_box->ymax;
276
277
0
  if ( FLAGS_GET_Z(merge_box->flags) || FLAGS_GET_GEODETIC(merge_box->flags) )
278
0
  {
279
0
    if ( new_box->zmin < merge_box->zmin) merge_box->zmin = new_box->zmin;
280
0
    if ( new_box->zmax > merge_box->zmax) merge_box->zmax = new_box->zmax;
281
0
  }
282
0
  if ( FLAGS_GET_M(merge_box->flags) )
283
0
  {
284
0
    if ( new_box->mmin < merge_box->mmin) merge_box->mmin = new_box->mmin;
285
0
    if ( new_box->mmax > merge_box->mmax) merge_box->mmax = new_box->mmax;
286
0
  }
287
288
0
  return LW_SUCCESS;
289
0
}
290
291
int gbox_overlaps(const GBOX *g1, const GBOX *g2)
292
0
{
293
294
  /* Make sure our boxes are consistent */
295
0
  if ( FLAGS_GET_GEODETIC(g1->flags) != FLAGS_GET_GEODETIC(g2->flags) )
296
0
    lwerror("gbox_overlaps: cannot compare geodetic and non-geodetic boxes");
297
298
  /* Check X/Y first */
299
0
  if ( g1->xmax < g2->xmin || g1->ymax < g2->ymin ||
300
0
       g1->xmin > g2->xmax || g1->ymin > g2->ymax )
301
0
    return LW_FALSE;
302
303
  /* Deal with the geodetic case special: we only compare the geodetic boxes (x/y/z) */
304
  /* Never the M dimension */
305
0
  if ( FLAGS_GET_GEODETIC(g1->flags) && FLAGS_GET_GEODETIC(g2->flags) )
306
0
  {
307
0
    if ( g1->zmax < g2->zmin || g1->zmin > g2->zmax )
308
0
      return LW_FALSE;
309
0
    else
310
0
      return LW_TRUE;
311
0
  }
312
313
  /* If both geodetic or both have Z, check Z */
314
0
  if ( FLAGS_GET_Z(g1->flags) && FLAGS_GET_Z(g2->flags) )
315
0
  {
316
0
    if ( g1->zmax < g2->zmin || g1->zmin > g2->zmax )
317
0
      return LW_FALSE;
318
0
  }
319
320
  /* If both have M, check M */
321
0
  if ( FLAGS_GET_M(g1->flags) && FLAGS_GET_M(g2->flags) )
322
0
  {
323
0
    if ( g1->mmax < g2->mmin || g1->mmin > g2->mmax )
324
0
      return LW_FALSE;
325
0
  }
326
327
0
  return LW_TRUE;
328
0
}
329
330
int
331
gbox_overlaps_2d(const GBOX *g1, const GBOX *g2)
332
0
{
333
334
  /* Make sure our boxes are consistent */
335
0
  if ( FLAGS_GET_GEODETIC(g1->flags) != FLAGS_GET_GEODETIC(g2->flags) )
336
0
    lwerror("gbox_overlaps: cannot compare geodetic and non-geodetic boxes");
337
338
  /* Check X/Y first */
339
0
  if ( g1->xmax < g2->xmin || g1->ymax < g2->ymin ||
340
0
       g1->xmin > g2->xmax || g1->ymin > g2->ymax )
341
0
    return LW_FALSE;
342
343
0
  return LW_TRUE;
344
0
}
345
346
int
347
gbox_contains_2d(const GBOX *g1, const GBOX *g2)
348
0
{
349
0
  if ( ( g2->xmin < g1->xmin ) || ( g2->xmax > g1->xmax ) ||
350
0
       ( g2->ymin < g1->ymin ) || ( g2->ymax > g1->ymax ) )
351
0
  {
352
0
    return LW_FALSE;
353
0
  }
354
0
  return LW_TRUE;
355
0
}
356
357
358
int
359
gbox_within_2d(const GBOX *g1, const GBOX *g2)
360
0
{
361
0
  if ( ( g1->xmin < g2->xmin ) || ( g1->xmax > g2->xmax ) ||
362
0
       ( g1->ymin < g2->ymin ) || ( g1->ymax > g2->ymax ) )
363
0
  {
364
0
    return LW_FALSE;
365
0
  }
366
0
  return LW_TRUE;
367
0
}
368
369
int
370
gbox_contains_point2d(const GBOX *g, const POINT2D *p)
371
0
{
372
0
  if ( ( g->xmin <= p->x ) && ( g->xmax >= p->x ) &&
373
0
       ( g->ymin <= p->y ) && ( g->ymax >= p->y ) )
374
0
  {
375
0
    return LW_TRUE;
376
0
  }
377
0
  return LW_FALSE;
378
0
}
379
380
/**
381
* Warning, this function is only good for x/y/z boxes, used
382
* in unit testing of geodetic box generation.
383
*/
384
GBOX* gbox_from_string(const char *str)
385
0
{
386
0
  const char *ptr = str;
387
0
  char *nextptr;
388
0
  char *gbox_start = strstr(str, "GBOX((");
389
0
  GBOX *gbox = gbox_new(lwflags(0,0,1));
390
0
  if ( ! gbox_start ) return NULL; /* No header found */
391
0
  ptr += 6;
392
0
  gbox->xmin = strtod(ptr, &nextptr);
393
0
  if ( ptr == nextptr ) return NULL; /* No double found */
394
0
  ptr = nextptr + 1;
395
0
  gbox->ymin = strtod(ptr, &nextptr);
396
0
  if ( ptr == nextptr ) return NULL; /* No double found */
397
0
  ptr = nextptr + 1;
398
0
  gbox->zmin = strtod(ptr, &nextptr);
399
0
  if ( ptr == nextptr ) return NULL; /* No double found */
400
0
  ptr = nextptr + 3;
401
0
  gbox->xmax = strtod(ptr, &nextptr);
402
0
  if ( ptr == nextptr ) return NULL; /* No double found */
403
0
  ptr = nextptr + 1;
404
0
  gbox->ymax = strtod(ptr, &nextptr);
405
0
  if ( ptr == nextptr ) return NULL; /* No double found */
406
0
  ptr = nextptr + 1;
407
0
  gbox->zmax = strtod(ptr, &nextptr);
408
0
  if ( ptr == nextptr ) return NULL; /* No double found */
409
0
  return gbox;
410
0
}
411
412
char* gbox_to_string(const GBOX *gbox)
413
0
{
414
0
  const size_t sz = 138;
415
0
  char *str = NULL;
416
417
0
  if ( ! gbox )
418
0
    return lwstrdup("NULL POINTER");
419
420
0
  str = (char*)lwalloc(sz);
421
422
0
  if ( FLAGS_GET_GEODETIC(gbox->flags) )
423
0
  {
424
0
    snprintf(str, sz, "GBOX((%.8g,%.8g,%.8g),(%.8g,%.8g,%.8g))", gbox->xmin, gbox->ymin, gbox->zmin, gbox->xmax, gbox->ymax, gbox->zmax);
425
0
    return str;
426
0
  }
427
0
  if ( FLAGS_GET_Z(gbox->flags) && FLAGS_GET_M(gbox->flags) )
428
0
  {
429
0
    snprintf(str, sz, "GBOX((%.8g,%.8g,%.8g,%.8g),(%.8g,%.8g,%.8g,%.8g))", gbox->xmin, gbox->ymin, gbox->zmin, gbox->mmin, gbox->xmax, gbox->ymax, gbox->zmax, gbox->mmax);
430
0
    return str;
431
0
  }
432
0
  if ( FLAGS_GET_Z(gbox->flags) )
433
0
  {
434
0
    snprintf(str, sz, "GBOX((%.8g,%.8g,%.8g),(%.8g,%.8g,%.8g))", gbox->xmin, gbox->ymin, gbox->zmin, gbox->xmax, gbox->ymax, gbox->zmax);
435
0
    return str;
436
0
  }
437
0
  if ( FLAGS_GET_M(gbox->flags) )
438
0
  {
439
0
    snprintf(str, sz, "GBOX((%.8g,%.8g,%.8g),(%.8g,%.8g,%.8g))", gbox->xmin, gbox->ymin, gbox->mmin, gbox->xmax, gbox->ymax, gbox->mmax);
440
0
    return str;
441
0
  }
442
0
  snprintf(str, sz, "GBOX((%.8g,%.8g),(%.8g,%.8g))", gbox->xmin, gbox->ymin, gbox->xmax, gbox->ymax);
443
0
  return str;
444
0
}
445
446
GBOX* gbox_copy(const GBOX *box)
447
0
{
448
0
  GBOX *copy = (GBOX*)lwalloc(sizeof(GBOX));
449
0
  memcpy(copy, box, sizeof(GBOX));
450
0
  return copy;
451
0
}
452
453
void gbox_duplicate(const GBOX *original, GBOX *duplicate)
454
0
{
455
0
  assert(duplicate);
456
0
  assert(original);
457
0
  memcpy(duplicate, original, sizeof(GBOX));
458
0
}
459
460
size_t gbox_serialized_size(lwflags_t flags)
461
0
{
462
0
  if (FLAGS_GET_GEODETIC(flags))
463
0
    return 6 * sizeof(float);
464
0
  else
465
0
    return 2 * FLAGS_NDIMS(flags) * sizeof(float);
466
0
}
467
468
469
/* ********************************************************************************
470
** Compute cartesian bounding GBOX boxes from LWGEOM.
471
*/
472
473
int lw_arc_calculate_gbox_cartesian_2d(const POINT2D *A1, const POINT2D *A2, const POINT2D *A3, GBOX *gbox)
474
0
{
475
0
  POINT2D xmin, ymin, xmax, ymax;
476
0
  POINT2D C;
477
0
  int A2_side;
478
0
  double radius_A;
479
480
0
  LWDEBUG(2, "lw_arc_calculate_gbox_cartesian_2d called.");
481
482
0
  radius_A = lw_arc_center(A1, A2, A3, &C);
483
484
  /* Negative radius signals straight line, p1/p2/p3 are collinear */
485
0
  if (radius_A < 0.0)
486
0
  {
487
0
        gbox->xmin = FP_MIN(A1->x, A3->x);
488
0
        gbox->ymin = FP_MIN(A1->y, A3->y);
489
0
        gbox->xmax = FP_MAX(A1->x, A3->x);
490
0
        gbox->ymax = FP_MAX(A1->y, A3->y);
491
0
      return LW_SUCCESS;
492
0
  }
493
494
  /* Matched start/end points imply circle */
495
0
  if ( A1->x == A3->x && A1->y == A3->y )
496
0
  {
497
0
    gbox->xmin = C.x - radius_A;
498
0
    gbox->ymin = C.y - radius_A;
499
0
    gbox->xmax = C.x + radius_A;
500
0
    gbox->ymax = C.y + radius_A;
501
0
    return LW_SUCCESS;
502
0
  }
503
504
  /* First approximation, bounds of start/end points */
505
0
    gbox->xmin = FP_MIN(A1->x, A3->x);
506
0
    gbox->ymin = FP_MIN(A1->y, A3->y);
507
0
    gbox->xmax = FP_MAX(A1->x, A3->x);
508
0
    gbox->ymax = FP_MAX(A1->y, A3->y);
509
510
  /* Create points for the possible extrema */
511
0
  xmin.x = C.x - radius_A;
512
0
  xmin.y = C.y;
513
0
  ymin.x = C.x;
514
0
  ymin.y = C.y - radius_A;
515
0
  xmax.x = C.x + radius_A;
516
0
  xmax.y = C.y;
517
0
  ymax.x = C.x;
518
0
  ymax.y = C.y + radius_A;
519
520
  /* Divide the circle into two parts, one on each side of a line
521
     joining p1 and p3. The circle extrema on the same side of that line
522
     as p2 is on, are also the extrema of the bbox. */
523
524
0
  A2_side = lw_segment_side(A1, A3, A2);
525
526
0
  if ( A2_side == lw_segment_side(A1, A3, &xmin) )
527
0
    gbox->xmin = xmin.x;
528
529
0
  if ( A2_side == lw_segment_side(A1, A3, &ymin) )
530
0
    gbox->ymin = ymin.y;
531
532
0
  if ( A2_side == lw_segment_side(A1, A3, &xmax) )
533
0
    gbox->xmax = xmax.x;
534
535
0
  if ( A2_side == lw_segment_side(A1, A3, &ymax) )
536
0
    gbox->ymax = ymax.y;
537
538
0
  return LW_SUCCESS;
539
0
}
540
541
542
static int lw_arc_calculate_gbox_cartesian(const POINT4D *p1, const POINT4D *p2, const POINT4D *p3, GBOX *gbox)
543
0
{
544
0
  int rv;
545
546
0
  LWDEBUG(2, "lw_arc_calculate_gbox_cartesian called.");
547
548
0
  rv = lw_arc_calculate_gbox_cartesian_2d((POINT2D*)p1, (POINT2D*)p2, (POINT2D*)p3, gbox);
549
0
    gbox->zmin = FP_MIN(p1->z, p3->z);
550
0
    gbox->mmin = FP_MIN(p1->m, p3->m);
551
0
    gbox->zmax = FP_MAX(p1->z, p3->z);
552
0
    gbox->mmax = FP_MAX(p1->m, p3->m);
553
0
  return rv;
554
0
}
555
556
static void
557
ptarray_calculate_gbox_cartesian_2d(const POINTARRAY *pa, GBOX *gbox)
558
0
{
559
0
  const POINT2D *p = getPoint2d_cp(pa, 0);
560
561
0
  gbox->xmax = gbox->xmin = p->x;
562
0
  gbox->ymax = gbox->ymin = p->y;
563
564
0
  for (uint32_t i = 1; i < pa->npoints; i++)
565
0
  {
566
0
    p = getPoint2d_cp(pa, i);
567
0
    gbox->xmin = FP_MIN(gbox->xmin, p->x);
568
0
    gbox->xmax = FP_MAX(gbox->xmax, p->x);
569
0
    gbox->ymin = FP_MIN(gbox->ymin, p->y);
570
0
    gbox->ymax = FP_MAX(gbox->ymax, p->y);
571
0
  }
572
0
}
573
574
/* Works with X/Y/Z. Needs to be adjusted after if X/Y/M was required */
575
static void
576
ptarray_calculate_gbox_cartesian_3d(const POINTARRAY *pa, GBOX *gbox)
577
0
{
578
0
  const POINT3D *p = getPoint3d_cp(pa, 0);
579
580
0
  gbox->xmax = gbox->xmin = p->x;
581
0
  gbox->ymax = gbox->ymin = p->y;
582
0
  gbox->zmax = gbox->zmin = p->z;
583
584
0
  for (uint32_t i = 1; i < pa->npoints; i++)
585
0
  {
586
0
    p = getPoint3d_cp(pa, i);
587
0
    gbox->xmin = FP_MIN(gbox->xmin, p->x);
588
0
    gbox->xmax = FP_MAX(gbox->xmax, p->x);
589
0
    gbox->ymin = FP_MIN(gbox->ymin, p->y);
590
0
    gbox->ymax = FP_MAX(gbox->ymax, p->y);
591
0
    gbox->zmin = FP_MIN(gbox->zmin, p->z);
592
0
    gbox->zmax = FP_MAX(gbox->zmax, p->z);
593
0
  }
594
0
}
595
596
static void
597
ptarray_calculate_gbox_cartesian_4d(const POINTARRAY *pa, GBOX *gbox)
598
0
{
599
0
  const POINT4D *p = getPoint4d_cp(pa, 0);
600
601
0
  gbox->xmax = gbox->xmin = p->x;
602
0
  gbox->ymax = gbox->ymin = p->y;
603
0
  gbox->zmax = gbox->zmin = p->z;
604
0
  gbox->mmax = gbox->mmin = p->m;
605
606
0
  for (uint32_t i = 1; i < pa->npoints; i++)
607
0
  {
608
0
    p = getPoint4d_cp(pa, i);
609
0
    gbox->xmin = FP_MIN(gbox->xmin, p->x);
610
0
    gbox->xmax = FP_MAX(gbox->xmax, p->x);
611
0
    gbox->ymin = FP_MIN(gbox->ymin, p->y);
612
0
    gbox->ymax = FP_MAX(gbox->ymax, p->y);
613
0
    gbox->zmin = FP_MIN(gbox->zmin, p->z);
614
0
    gbox->zmax = FP_MAX(gbox->zmax, p->z);
615
0
    gbox->mmin = FP_MIN(gbox->mmin, p->m);
616
0
    gbox->mmax = FP_MAX(gbox->mmax, p->m);
617
0
  }
618
0
}
619
620
int
621
ptarray_calculate_gbox_cartesian(const POINTARRAY *pa, GBOX *gbox)
622
0
{
623
0
  if (!pa || pa->npoints == 0)
624
0
    return LW_FAILURE;
625
0
  if (!gbox)
626
0
    return LW_FAILURE;
627
628
0
  int has_z = FLAGS_GET_Z(pa->flags);
629
0
  int has_m = FLAGS_GET_M(pa->flags);
630
0
  gbox->flags = lwflags(has_z, has_m, 0);
631
0
  LWDEBUGF(4, "ptarray_calculate_gbox Z: %d M: %d", has_z, has_m);
632
0
  int coordinates = 2 + has_z + has_m;
633
634
0
  switch (coordinates)
635
0
  {
636
0
  case 2:
637
0
  {
638
0
    ptarray_calculate_gbox_cartesian_2d(pa, gbox);
639
0
    break;
640
0
  }
641
0
  case 3:
642
0
  {
643
0
    if (has_z)
644
0
    {
645
0
      ptarray_calculate_gbox_cartesian_3d(pa, gbox);
646
0
    }
647
0
    else
648
0
    {
649
0
      double zmin = gbox->zmin;
650
0
      double zmax = gbox->zmax;
651
0
      ptarray_calculate_gbox_cartesian_3d(pa, gbox);
652
0
      gbox->mmin = gbox->zmin;
653
0
      gbox->mmax = gbox->zmax;
654
0
      gbox->zmin = zmin;
655
0
      gbox->zmax = zmax;
656
0
    }
657
0
    break;
658
0
  }
659
0
  default:
660
0
  {
661
0
    ptarray_calculate_gbox_cartesian_4d(pa, gbox);
662
0
    break;
663
0
  }
664
0
  }
665
0
  return LW_SUCCESS;
666
0
}
667
668
static int lwcircstring_calculate_gbox_cartesian(LWCIRCSTRING *curve, GBOX *gbox)
669
0
{
670
0
  GBOX tmp = {0};
671
0
  POINT4D p1, p2, p3;
672
0
  uint32_t i;
673
674
0
  if (!curve) return LW_FAILURE;
675
0
  if (curve->points->npoints < 3) return LW_FAILURE;
676
677
0
  tmp.flags =
678
0
      lwflags(FLAGS_GET_Z(curve->flags), FLAGS_GET_M(curve->flags), 0);
679
680
  /* Initialize */
681
0
  gbox->xmin = gbox->ymin = gbox->zmin = gbox->mmin = FLT_MAX;
682
0
  gbox->xmax = gbox->ymax = gbox->zmax = gbox->mmax = -1*FLT_MAX;
683
684
0
  for ( i = 2; i < curve->points->npoints; i += 2 )
685
0
  {
686
0
    getPoint4d_p(curve->points, i-2, &p1);
687
0
    getPoint4d_p(curve->points, i-1, &p2);
688
0
    getPoint4d_p(curve->points, i, &p3);
689
690
0
    if (lw_arc_calculate_gbox_cartesian(&p1, &p2, &p3, &tmp) == LW_FAILURE)
691
0
      continue;
692
693
0
    gbox_merge(&tmp, gbox);
694
0
  }
695
696
0
  return LW_SUCCESS;
697
0
}
698
699
/**
700
 * Projects a homogeneous control point by dividing its weighted coordinates
701
 * by w, failing when w is zero and the Cartesian point is undefined.
702
 */
703
static int
704
lwnurbscurve_hpoint_project(const NURBS_BBOX_HPOINT *hpoint, POINT4D *point)
705
0
{
706
0
  if (hpoint->w == 0.0)
707
0
    return LW_FAILURE;
708
709
0
  point->x = hpoint->x / hpoint->w;
710
0
  point->y = hpoint->y / hpoint->w;
711
0
  point->z = hpoint->z / hpoint->w;
712
0
  point->m = hpoint->m / hpoint->w;
713
0
  return LW_SUCCESS;
714
0
}
715
716
/**
717
 * Interpolates in homogeneous space, as required by rational de Casteljau
718
 * subdivision and Boehm knot insertion; interpolating projected points would
719
 * describe a different curve.
720
 */
721
static NURBS_BBOX_HPOINT
722
lwnurbscurve_hpoint_lerp(const NURBS_BBOX_HPOINT *a, const NURBS_BBOX_HPOINT *b, double t)
723
0
{
724
0
  NURBS_BBOX_HPOINT out;
725
0
  double s = 1.0 - t;
726
727
0
  out.x = s * a->x + t * b->x;
728
0
  out.y = s * a->y + t * b->y;
729
0
  out.z = s * a->z + t * b->z;
730
0
  out.m = s * a->m + t * b->m;
731
0
  out.w = s * a->w + t * b->w;
732
0
  return out;
733
0
}
734
735
/**
736
 * Loads one control point in homogeneous coordinates, applying the implicit
737
 * weight 1.0 for polynomial curves so Bezier extraction can use the same
738
 * arithmetic for rational and non-rational spans.
739
 */
740
static void
741
lwnurbscurve_get_hpoint(const LWNURBSCURVE *curve, uint32_t index, NURBS_BBOX_HPOINT *hpoint)
742
0
{
743
0
  POINT4D point;
744
0
  double weight = (curve->weights && index < curve->nweights) ? curve->weights[index] : 1.0;
745
746
0
  getPoint4d_p(curve->points, index, &point);
747
0
  hpoint->x = point.x * weight;
748
0
  hpoint->y = point.y * weight;
749
0
  hpoint->z = FLAGS_GET_Z(curve->flags) ? point.z * weight : 0.0;
750
0
  hpoint->m = FLAGS_GET_M(curve->flags) ? point.m * weight : 0.0;
751
0
  hpoint->w = weight;
752
0
}
753
754
/**
755
 * Expands a Cartesian box in each enabled dimension, keeping dimensionality
756
 * decisions out of the projection and subdivision helpers.
757
 */
758
static void
759
lwnurbscurve_add_point_to_gbox(const POINT4D *point, GBOX *gbox)
760
0
{
761
0
  gbox->xmin = FP_MIN(gbox->xmin, point->x);
762
0
  gbox->xmax = FP_MAX(gbox->xmax, point->x);
763
0
  gbox->ymin = FP_MIN(gbox->ymin, point->y);
764
0
  gbox->ymax = FP_MAX(gbox->ymax, point->y);
765
766
0
  if (FLAGS_GET_Z(gbox->flags))
767
0
  {
768
0
    gbox->zmin = FP_MIN(gbox->zmin, point->z);
769
0
    gbox->zmax = FP_MAX(gbox->zmax, point->z);
770
0
  }
771
772
0
  if (FLAGS_GET_M(gbox->flags))
773
0
  {
774
0
    gbox->mmin = FP_MIN(gbox->mmin, point->m);
775
0
    gbox->mmax = FP_MAX(gbox->mmax, point->m);
776
0
  }
777
0
}
778
779
/**
780
 * Projects and merges one homogeneous control point, centralizing zero-weight
781
 * rejection before any coordinate reaches a box accumulator.
782
 */
783
static int
784
lwnurbscurve_add_hpoint_to_gbox(const NURBS_BBOX_HPOINT *hpoint, GBOX *gbox)
785
0
{
786
0
  POINT4D point;
787
788
0
  if (lwnurbscurve_hpoint_project(hpoint, &point) == LW_FAILURE)
789
0
    return LW_FAILURE;
790
791
0
  lwnurbscurve_add_point_to_gbox(&point, gbox);
792
0
  return LW_SUCCESS;
793
0
}
794
795
/**
796
 * Initializes an empty double-precision accumulator for the requested
797
 * dimensions, using inverted extrema for later additions. Float rounding is
798
 * deferred until the subdivision stop test.
799
 */
800
static void
801
lwnurbscurve_gbox_init(GBOX *gbox, lwflags_t flags)
802
0
{
803
0
  gbox->flags = flags;
804
0
  gbox->xmin = gbox->ymin = gbox->zmin = gbox->mmin = DBL_MAX;
805
0
  gbox->xmax = gbox->ymax = gbox->zmax = gbox->mmax = -DBL_MAX;
806
0
}
807
808
/**
809
 * Bounds the projected control-point hull of a positive-weight rational Bezier
810
 * span, providing the conservative outer bound used during subdivision.
811
 */
812
static int
813
lwnurbscurve_bezier_hull_gbox(const NURBS_BBOX_HPOINT *points, uint32_t npoints, lwflags_t flags, GBOX *gbox)
814
0
{
815
0
  uint32_t i;
816
817
0
  lwnurbscurve_gbox_init(gbox, flags);
818
0
  for (i = 0; i < npoints; i++)
819
0
  {
820
0
    if (lwnurbscurve_add_hpoint_to_gbox(&points[i], gbox) == LW_FAILURE)
821
0
      return LW_FAILURE;
822
0
  }
823
824
0
  return LW_SUCCESS;
825
0
}
826
827
/**
828
 * Bounds the projected endpoints of a Bezier span, providing an inner
829
 * coordinate-range bound to compare with the control hull's outer bound.
830
 */
831
static int
832
lwnurbscurve_bezier_endpoint_gbox(const NURBS_BBOX_HPOINT *points, uint32_t degree, lwflags_t flags, GBOX *gbox)
833
0
{
834
0
  lwnurbscurve_gbox_init(gbox, flags);
835
836
0
  if (lwnurbscurve_add_hpoint_to_gbox(&points[0], gbox) == LW_FAILURE ||
837
0
      lwnurbscurve_add_hpoint_to_gbox(&points[degree], gbox) == LW_FAILURE)
838
0
    return LW_FAILURE;
839
840
0
  return LW_SUCCESS;
841
0
}
842
843
/**
844
 * Tests whether two boxes round outward to the same float bounds, so refinement
845
 * stops once it can no longer change the serialized bounding box. The caller
846
 * supplies boxes with identical dimensional flags.
847
 */
848
static int
849
lwnurbscurve_gbox_same_float(const GBOX *a, const GBOX *b)
850
0
{
851
0
  if (next_float_down(a->xmin) != next_float_down(b->xmin) || next_float_up(a->xmax) != next_float_up(b->xmax) ||
852
0
      next_float_down(a->ymin) != next_float_down(b->ymin) || next_float_up(a->ymax) != next_float_up(b->ymax))
853
0
    return LW_FALSE;
854
855
0
  if (FLAGS_GET_Z(a->flags) &&
856
0
      (next_float_down(a->zmin) != next_float_down(b->zmin) || next_float_up(a->zmax) != next_float_up(b->zmax)))
857
0
    return LW_FALSE;
858
859
0
  if (FLAGS_GET_M(a->flags) &&
860
0
      (next_float_down(a->mmin) != next_float_down(b->mmin) || next_float_up(a->mmax) != next_float_up(b->mmax)))
861
0
    return LW_FALSE;
862
863
0
  return LW_TRUE;
864
0
}
865
866
/**
867
 * Detects when floating-point subdivision no longer changes a control net.
868
 * Exact equality is intentional: any changed component permits more progress;
869
 * otherwise recursion stops with the current conservative hull.
870
 */
871
static int
872
lwnurbscurve_hpoints_same(const NURBS_BBOX_HPOINT *a, const NURBS_BBOX_HPOINT *b, uint32_t npoints)
873
0
{
874
0
  uint32_t i;
875
876
0
  for (i = 0; i < npoints; i++)
877
0
  {
878
0
    if (a[i].x != b[i].x || a[i].y != b[i].y || a[i].z != b[i].z || a[i].m != b[i].m || a[i].w != b[i].w)
879
0
      return LW_FALSE;
880
0
  }
881
882
0
  return LW_TRUE;
883
0
}
884
885
/**
886
 * Splits a homogeneous Bezier control net at t = 1/2, producing two control
887
 * nets that represent the same curve on their respective half-intervals. For
888
 * positive weights, each child's projected control hull is contained in the
889
 * parent's hull, so subdivision can only tighten the conservative outer box.
890
 */
891
static void
892
lwnurbscurve_bezier_split(const NURBS_BBOX_HPOINT *points,
893
        uint32_t degree,
894
        NURBS_BBOX_HPOINT *left,
895
        NURBS_BBOX_HPOINT *right)
896
0
{
897
0
  uint32_t i, r;
898
0
  NURBS_BBOX_HPOINT tmp[degree + 1];
899
900
0
  for (i = 0; i <= degree; i++)
901
0
    tmp[i] = points[i];
902
903
0
  left[0] = tmp[0];
904
0
  right[degree] = tmp[degree];
905
906
0
  for (r = 1; r <= degree; r++)
907
0
  {
908
0
    for (i = 0; i <= degree - r; i++)
909
0
      tmp[i] = lwnurbscurve_hpoint_lerp(&tmp[i], &tmp[i + 1], 0.5);
910
911
0
    left[r] = tmp[0];
912
0
    right[degree - r] = tmp[degree - r];
913
0
  }
914
0
}
915
916
/**
917
 * Recursively bounds one rational Bezier span. Its projected control hull is a
918
 * conservative outer box, while its endpoints give an inner box. Subdivision
919
 * narrows the two child hulls and adds their shared split point to the endpoint
920
 * sample set. Once inner and outer boxes outward-round to the same float box,
921
 * the stored GSERIALIZED box is fixed.
922
 *
923
 * This helper owns the stop test, splitting, and recursion. In particular, it
924
 * does not merge an oversized parent hull before descending: only terminal
925
 * child hulls are merged into gbox.
926
 */
927
static int
928
lwnurbscurve_add_bezier_span_gbox(const NURBS_BBOX_HPOINT *points,
929
          uint32_t degree,
930
          lwflags_t flags,
931
          uint32_t depth,
932
          GBOX *gbox)
933
0
{
934
0
  GBOX hull_gbox, endpoint_gbox;
935
0
  NURBS_BBOX_HPOINT left[degree + 1];
936
0
  NURBS_BBOX_HPOINT right[degree + 1];
937
938
0
  if (lwnurbscurve_bezier_hull_gbox(points, degree + 1, flags, &hull_gbox) == LW_FAILURE ||
939
0
      lwnurbscurve_bezier_endpoint_gbox(points, degree, flags, &endpoint_gbox) == LW_FAILURE)
940
0
    return LW_FAILURE;
941
942
  /* A non-finite control point coordinate or weight (NaN or +/-Inf, accepted
943
   * by WKT input the same way LINESTRING accepts them) makes both stop tests
944
   * below unusable: NaN never compares equal to itself, so neither
945
   * lwnurbscurve_gbox_same_float() nor lwnurbscurve_hpoints_same() can ever
946
   * report convergence, and subdivision would run to the full DBL_MANT_DIG
947
   * recursion depth on every remaining span. hull_gbox already bounds every
948
   * control point in this span, so stop here and merge it directly, the same
949
   * way ptarray_calculate_gbox_cartesian() lets a non-finite point propagate
950
   * straight into the box for LINESTRING and the other simple types instead
951
   * of iterating further. */
952
0
  if (!gbox_is_valid(&hull_gbox))
953
0
  {
954
0
    gbox_merge(&hull_gbox, gbox);
955
0
    return LW_SUCCESS;
956
0
  }
957
958
  /* Positive NURBS weights put each rational Bezier span inside the convex
959
   * hull of its projected control points. Subdivision tightens that hull
960
   * until further refinement cannot change the float bbox that PostGIS stores
961
   * in GSERIALIZED. If arithmetic stops changing the control net first, merge
962
   * the current hull; it is still conservative. */
963
0
  if (depth >= DBL_MANT_DIG || lwnurbscurve_gbox_same_float(&hull_gbox, &endpoint_gbox))
964
0
  {
965
0
    gbox_merge(&hull_gbox, gbox);
966
0
    return LW_SUCCESS;
967
0
  }
968
969
  /* Keep splitting behind this helper so every child is subjected to the same
970
   * conservative stop and fallback rules before anything is merged. */
971
0
  lwnurbscurve_bezier_split(points, degree, left, right);
972
0
  if (lwnurbscurve_hpoints_same(left, points, degree + 1) || lwnurbscurve_hpoints_same(right, points, degree + 1))
973
0
  {
974
0
    gbox_merge(&hull_gbox, gbox);
975
0
    return LW_SUCCESS;
976
0
  }
977
978
0
  if (lwnurbscurve_add_bezier_span_gbox(left, degree, flags, depth + 1, gbox) == LW_FAILURE ||
979
0
      lwnurbscurve_add_bezier_span_gbox(right, degree, flags, depth + 1, gbox) == LW_FAILURE)
980
0
    return LW_FAILURE;
981
982
0
  return LW_SUCCESS;
983
0
}
984
985
static int lwpoint_calculate_gbox_cartesian(LWPOINT *point, GBOX *gbox)
986
0
{
987
0
  if ( ! point ) return LW_FAILURE;
988
0
  return ptarray_calculate_gbox_cartesian( point->point, gbox );
989
0
}
990
991
static int lwline_calculate_gbox_cartesian(LWLINE *line, GBOX *gbox)
992
0
{
993
0
  if ( ! line ) return LW_FAILURE;
994
0
  return ptarray_calculate_gbox_cartesian( line->points, gbox );
995
0
}
996
997
/**
998
 * Computes a conservative Cartesian box for the whole NURBS curve by
999
 * subdividing Bezier spans, which can tighten the box beyond what the original
1000
 * control points or endpoints show. It submits each complete span to
1001
 * lwnurbscurve_add_bezier_span_gbox(), which decides whether to split and owns
1002
 * the recursive merge; splitting here would duplicate that policy.
1003
 */
1004
static int
1005
lwnurbscurve_calculate_gbox_cartesian(const LWNURBSCURVE *curve, GBOX *gbox)
1006
0
{
1007
0
  uint32_t a, b, degree, i, j, m, multiplicity, npoints, r, s;
1008
0
  int found_span = LW_FALSE;
1009
0
  double domain_max, domain_min;
1010
0
  double *knots;
1011
0
  uint32_t nknots;
1012
1013
0
  if (!curve || !curve->points || curve->points->npoints == 0)
1014
0
    return LW_FAILURE;
1015
1016
0
  degree = curve->degree;
1017
0
  npoints = curve->points->npoints;
1018
0
  knots = lwnurbscurve_get_or_generate_knots(curve, &nknots);
1019
0
  if (!knots || nknots == 0)
1020
0
    return LW_FAILURE;
1021
1022
0
  domain_min = knots[degree];
1023
0
  domain_max = knots[npoints];
1024
0
  if (domain_max <= domain_min || nknots != npoints + degree + 1)
1025
0
  {
1026
0
    lwfree(knots);
1027
0
    return LW_FAILURE;
1028
0
  }
1029
1030
0
  lwnurbscurve_gbox_init(gbox, lwflags(FLAGS_GET_Z(curve->flags), FLAGS_GET_M(curve->flags), 0));
1031
1032
  /* Stream the standard B-spline to Bezier decomposition with one current
1033
   * and one next control net. This is Boehm knot insertion arranged so the
1034
   * insertions for one knot block are applied only to the local control net,
1035
   * preserving the exact span geometry while avoiding whole-curve knot
1036
   * refinement and its quadratic memory churn. */
1037
0
  {
1038
0
    NURBS_BBOX_HPOINT bezier[degree + 1];
1039
0
    NURBS_BBOX_HPOINT next_bezier[degree + 1];
1040
0
    double alphas[degree ? degree : 1];
1041
1042
0
    for (i = 0; i <= degree; i++)
1043
0
      lwnurbscurve_get_hpoint(curve, i, &bezier[i]);
1044
1045
0
    m = npoints + degree;
1046
0
    a = degree;
1047
0
    b = degree + 1;
1048
1049
0
    while (b < m)
1050
0
    {
1051
0
      uint32_t knot_block_start = b;
1052
1053
      /* Exact comparison is intentional: these values come directly from
1054
       * the validated knot vector or its generated uniform replacement. */
1055
0
      while (b < m && knots[b + 1] == knots[b])
1056
0
        b++;
1057
1058
0
      multiplicity = b - knot_block_start + 1;
1059
0
      if (multiplicity < degree)
1060
0
      {
1061
0
        double numerator = knots[b] - knots[a];
1062
1063
        /* Precompute the knot-insertion blend factors for this block.
1064
         * A zero denominator would mean the validated active span has
1065
         * collapsed, so fail instead of feeding NaNs to the bbox code. */
1066
0
        for (j = degree; j > multiplicity; j--)
1067
0
        {
1068
0
          double denom = knots[a + j] - knots[a];
1069
1070
0
          if (denom == 0.0)
1071
0
          {
1072
0
            lwfree(knots);
1073
0
            return LW_FAILURE;
1074
0
          }
1075
1076
0
          alphas[j - multiplicity - 1] = numerator / denom;
1077
0
        }
1078
1079
0
        r = degree - multiplicity;
1080
0
        for (j = 1; j <= r; j++)
1081
0
        {
1082
0
          uint32_t save = r - j;
1083
1084
          /* Each pass raises the current knot multiplicity by one.
1085
           * The rightmost point saved on every pass becomes the left
1086
           * side of the next span's control net. */
1087
0
          s = multiplicity + j;
1088
0
          for (i = degree; i >= s; i--)
1089
0
          {
1090
0
            double alpha = alphas[i - s];
1091
0
            bezier[i] = lwnurbscurve_hpoint_lerp(&bezier[i - 1], &bezier[i], alpha);
1092
0
          }
1093
1094
0
          if (b < m)
1095
0
            next_bezier[save] = bezier[degree];
1096
0
        }
1097
0
      }
1098
1099
      /* Only bbox spans inside the NURBS active parameter domain
1100
       * [U[p], U[n]]. Unclamped explicit knot vectors can contain valid
1101
       * knots before or after that domain, and those must not contribute. */
1102
0
      if (knots[b] > knots[a] && knots[a] >= domain_min && knots[b] <= domain_max)
1103
0
      {
1104
0
        if (lwnurbscurve_add_bezier_span_gbox(bezier, degree, gbox->flags, 0, gbox) ==
1105
0
            LW_FAILURE)
1106
0
        {
1107
0
          lwfree(knots);
1108
0
          return LW_FAILURE;
1109
0
        }
1110
0
        found_span = LW_TRUE;
1111
0
      }
1112
1113
0
      if (knots[b] >= domain_max || b >= m)
1114
0
        break;
1115
1116
      /* Complete the next span from the saved right edge and the untouched
1117
       * original control points that are entering the moving local window. */
1118
0
      for (i = degree - FP_MIN(multiplicity, degree); i <= degree; i++)
1119
0
        lwnurbscurve_get_hpoint(curve, b - degree + i, &next_bezier[i]);
1120
1121
0
      for (i = 0; i <= degree; i++)
1122
0
        bezier[i] = next_bezier[i];
1123
1124
0
      a = b;
1125
0
      b++;
1126
0
    }
1127
0
  }
1128
1129
0
  lwfree(knots);
1130
1131
0
  return found_span ? LW_SUCCESS : LW_FAILURE;
1132
0
}
1133
1134
static int lwtriangle_calculate_gbox_cartesian(LWTRIANGLE *triangle, GBOX *gbox)
1135
0
{
1136
0
  if ( ! triangle ) return LW_FAILURE;
1137
0
  return ptarray_calculate_gbox_cartesian( triangle->points, gbox );
1138
0
}
1139
1140
static int lwpoly_calculate_gbox_cartesian(LWPOLY *poly, GBOX *gbox)
1141
0
{
1142
0
  if ( ! poly ) return LW_FAILURE;
1143
0
  if ( poly->nrings == 0 ) return LW_FAILURE;
1144
  /* Just need to check outer ring */
1145
0
  return ptarray_calculate_gbox_cartesian( poly->rings[0], gbox );
1146
0
}
1147
1148
static int lwcollection_calculate_gbox_cartesian(LWCOLLECTION *coll, GBOX *gbox)
1149
0
{
1150
0
  GBOX subbox = {0};
1151
0
  uint32_t i;
1152
0
  int result = LW_FAILURE;
1153
0
  int first = LW_TRUE;
1154
0
  assert(coll);
1155
0
  if ( (coll->ngeoms == 0) || !gbox)
1156
0
    return LW_FAILURE;
1157
1158
0
  subbox.flags = coll->flags;
1159
1160
0
  for ( i = 0; i < coll->ngeoms; i++ )
1161
0
  {
1162
0
    if ( lwgeom_calculate_gbox_cartesian((LWGEOM*)(coll->geoms[i]), &subbox) == LW_SUCCESS )
1163
0
    {
1164
      /* Keep a copy of the sub-bounding box for later
1165
      if ( coll->geoms[i]->bbox )
1166
        lwfree(coll->geoms[i]->bbox);
1167
      coll->geoms[i]->bbox = gbox_copy(&subbox); */
1168
0
      if ( first )
1169
0
      {
1170
0
        gbox_duplicate(&subbox, gbox);
1171
0
        first = LW_FALSE;
1172
0
      }
1173
0
      else
1174
0
      {
1175
0
        gbox_merge(&subbox, gbox);
1176
0
      }
1177
0
      result = LW_SUCCESS;
1178
0
    }
1179
0
  }
1180
0
  return result;
1181
0
}
1182
1183
/**
1184
 * Calculate a Cartesian bounding box for an LWGEOM and store it in a GBOX.
1185
 *
1186
 * Computes the 2D/3D/M bounding box for the given geometry and writes the result
1187
 * into the provided gbox. The function dispatches to type-specific helpers for
1188
 * supported geometry types.
1189
 *
1190
 * @param lwgeom Geometry to compute the bounding box for. If NULL, the function returns LW_FAILURE.
1191
 * @param gbox Destination GBOX to receive the computed bounding box (must be non-NULL).
1192
 * @return LW_SUCCESS on successful computation and population of gbox; LW_FAILURE if lwgeom is NULL,
1193
 *         if the geometry type is unsupported, or if a bounding box cannot be computed (e.g. empty NURBS).
1194
 */
1195
int lwgeom_calculate_gbox_cartesian(const LWGEOM *lwgeom, GBOX *gbox)
1196
0
{
1197
0
  if ( ! lwgeom ) return LW_FAILURE;
1198
0
  LWDEBUGF(4, "lwgeom_calculate_gbox got type (%d) - %s", lwgeom->type, lwtype_name(lwgeom->type));
1199
1200
0
  switch (lwgeom->type)
1201
0
  {
1202
0
  case POINTTYPE:
1203
0
    return lwpoint_calculate_gbox_cartesian((LWPOINT *)lwgeom, gbox);
1204
0
  case LINETYPE:
1205
0
    return lwline_calculate_gbox_cartesian((LWLINE *)lwgeom, gbox);
1206
0
  case CIRCSTRINGTYPE:
1207
0
    return lwcircstring_calculate_gbox_cartesian((LWCIRCSTRING *)lwgeom, gbox);
1208
0
  case POLYGONTYPE:
1209
0
    return lwpoly_calculate_gbox_cartesian((LWPOLY *)lwgeom, gbox);
1210
0
  case TRIANGLETYPE:
1211
0
    return lwtriangle_calculate_gbox_cartesian((LWTRIANGLE *)lwgeom, gbox);
1212
0
  case COMPOUNDTYPE:
1213
0
  case CURVEPOLYTYPE:
1214
0
  case MULTIPOINTTYPE:
1215
0
  case MULTILINETYPE:
1216
0
  case MULTICURVETYPE:
1217
0
  case MULTIPOLYGONTYPE:
1218
0
  case MULTISURFACETYPE:
1219
0
  case POLYHEDRALSURFACETYPE:
1220
0
  case TINTYPE:
1221
0
  case COLLECTIONTYPE:
1222
0
    return lwcollection_calculate_gbox_cartesian((LWCOLLECTION *)lwgeom, gbox);
1223
0
  case NURBSCURVETYPE:
1224
0
    return lwnurbscurve_calculate_gbox_cartesian((const LWNURBSCURVE *)lwgeom, gbox);
1225
0
  }
1226
  /* Never get here, please. */
1227
0
  lwerror("unsupported type (%d) - %s", lwgeom->type, lwtype_name(lwgeom->type));
1228
0
  return LW_FAILURE;
1229
0
}
1230
1231
void gbox_float_round(GBOX *gbox)
1232
0
{
1233
0
  gbox->xmin = next_float_down(gbox->xmin);
1234
0
  gbox->xmax = next_float_up(gbox->xmax);
1235
1236
0
  gbox->ymin = next_float_down(gbox->ymin);
1237
0
  gbox->ymax = next_float_up(gbox->ymax);
1238
1239
0
  if ( FLAGS_GET_M(gbox->flags) )
1240
0
  {
1241
0
    gbox->mmin = next_float_down(gbox->mmin);
1242
0
    gbox->mmax = next_float_up(gbox->mmax);
1243
0
  }
1244
1245
0
  if ( FLAGS_GET_Z(gbox->flags) )
1246
0
  {
1247
0
    gbox->zmin = next_float_down(gbox->zmin);
1248
0
    gbox->zmax = next_float_up(gbox->zmax);
1249
0
  }
1250
0
}
1251
1252
uint64_t
1253
gbox_get_sortable_hash(const GBOX *g, const int32_t srid)
1254
0
{
1255
0
  union floatuint {
1256
0
    uint32_t u;
1257
0
    float f;
1258
0
  };
1259
1260
0
  union floatuint x, y;
1261
1262
  /*
1263
  * Since in theory the bitwise representation of an IEEE
1264
  * float is sortable (exponents come before mantissa, etc)
1265
  * we just copy the bits directly into an int and then
1266
  * interleave those ints.
1267
  */
1268
0
  if (FLAGS_GET_GEODETIC(g->flags))
1269
0
  {
1270
0
    GEOGRAPHIC_POINT gpt;
1271
0
    POINT3D p;
1272
0
    p.x = (g->xmax + g->xmin) / 2.0;
1273
0
    p.y = (g->ymax + g->ymin) / 2.0;
1274
0
    p.z = (g->zmax + g->zmin) / 2.0;
1275
0
    normalize(&p);
1276
0
    cart2geog(&p, &gpt);
1277
    /* We know range for geography, so build the curve taking it into account */
1278
0
    x.f = 1.5 + gpt.lon / 512.0;
1279
0
    y.f = 1.5 + gpt.lat / 256.0;
1280
0
  }
1281
0
  else
1282
0
  {
1283
0
    x.f = (g->xmax + g->xmin) / 2;
1284
0
    y.f = (g->ymax + g->ymin) / 2;
1285
    /*
1286
     * Tweak for popular SRID values: push floating point values into 1..2 range,
1287
     * a region where exponent is constant and thus Hilbert curve
1288
     * doesn't have compression artifact when X or Y value is close to 0.
1289
     * If someone has out of bounds value it will still expose the arifact but not crash.
1290
     * TODO: reconsider when we will have machinery to properly get bounds by SRID.
1291
     */
1292
0
    if (srid == 3857 || srid == 3395)
1293
0
    {
1294
0
      x.f = 1.5 + x.f / 67108864.0;
1295
0
      y.f = 1.5 + y.f / 67108864.0;
1296
0
    }
1297
0
    else if (srid == 4326)
1298
0
    {
1299
0
      x.f = 1.5 + x.f / 512.0;
1300
0
      y.f = 1.5 + y.f / 256.0;
1301
0
    }
1302
0
  }
1303
1304
0
  return uint32_hilbert(y.u, x.u);
1305
0
}