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
946
{
42
946
  GBOX *g = (GBOX*)lwalloc(sizeof(GBOX));
43
946
  gbox_init(g);
44
946
  g->flags = flags;
45
946
  return g;
46
946
}
47
48
void gbox_init(GBOX *gbox)
49
1.58k
{
50
1.58k
  memset(gbox, 0, sizeof(GBOX));
51
1.58k
}
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
2.02M
{
207
  /* X */
208
2.02M
  if ( ! isfinite(gbox->xmin) || isnan(gbox->xmin) ||
209
2.02M
       ! isfinite(gbox->xmax) || isnan(gbox->xmax) )
210
2.91k
    return LW_FALSE;
211
212
  /* Y */
213
2.01M
  if ( ! isfinite(gbox->ymin) || isnan(gbox->ymin) ||
214
2.01M
       ! isfinite(gbox->ymax) || isnan(gbox->ymax) )
215
1.63k
    return LW_FALSE;
216
217
  /* Z */
218
2.01M
  if ( FLAGS_GET_GEODETIC(gbox->flags) || FLAGS_GET_Z(gbox->flags) )
219
840k
  {
220
840k
    if ( ! isfinite(gbox->zmin) || isnan(gbox->zmin) ||
221
840k
         ! isfinite(gbox->zmax) || isnan(gbox->zmax) )
222
604
      return LW_FALSE;
223
840k
  }
224
225
  /* M */
226
2.01M
  if ( FLAGS_GET_M(gbox->flags) )
227
305k
  {
228
305k
    if ( ! isfinite(gbox->mmin) || isnan(gbox->mmin) ||
229
305k
         ! isfinite(gbox->mmax) || isnan(gbox->mmax) )
230
612
      return LW_FALSE;
231
305k
  }
232
233
2.01M
  return LW_TRUE;
234
2.01M
}
235
236
int gbox_merge_point3d(const POINT3D *p, GBOX *gbox)
237
1.12M
{
238
1.12M
  if ( gbox->xmin > p->x ) gbox->xmin = p->x;
239
1.12M
  if ( gbox->ymin > p->y ) gbox->ymin = p->y;
240
1.12M
  if ( gbox->zmin > p->z ) gbox->zmin = p->z;
241
1.12M
  if ( gbox->xmax < p->x ) gbox->xmax = p->x;
242
1.12M
  if ( gbox->ymax < p->y ) gbox->ymax = p->y;
243
1.12M
  if ( gbox->zmax < p->z ) gbox->zmax = p->z;
244
1.12M
  return LW_SUCCESS;
245
1.12M
}
246
247
int gbox_init_point3d(const POINT3D *p, GBOX *gbox)
248
1.11M
{
249
1.11M
  gbox->xmin = gbox->xmax = p->x;
250
1.11M
  gbox->ymin = gbox->ymax = p->y;
251
1.11M
  gbox->zmin = gbox->zmax = p->z;
252
1.11M
  return LW_SUCCESS;
253
1.11M
}
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
6.45M
{
267
6.45M
  assert(merge_box);
268
269
6.45M
  if ( FLAGS_GET_ZM(merge_box->flags) != FLAGS_GET_ZM(new_box->flags) )
270
0
    return LW_FAILURE;
271
272
6.45M
  if ( new_box->xmin < merge_box->xmin) merge_box->xmin = new_box->xmin;
273
6.45M
  if ( new_box->ymin < merge_box->ymin) merge_box->ymin = new_box->ymin;
274
6.45M
  if ( new_box->xmax > merge_box->xmax) merge_box->xmax = new_box->xmax;
275
6.45M
  if ( new_box->ymax > merge_box->ymax) merge_box->ymax = new_box->ymax;
276
277
6.45M
  if ( FLAGS_GET_Z(merge_box->flags) || FLAGS_GET_GEODETIC(merge_box->flags) )
278
2.87M
  {
279
2.87M
    if ( new_box->zmin < merge_box->zmin) merge_box->zmin = new_box->zmin;
280
2.87M
    if ( new_box->zmax > merge_box->zmax) merge_box->zmax = new_box->zmax;
281
2.87M
  }
282
6.45M
  if ( FLAGS_GET_M(merge_box->flags) )
283
1.67M
  {
284
1.67M
    if ( new_box->mmin < merge_box->mmin) merge_box->mmin = new_box->mmin;
285
1.67M
    if ( new_box->mmax > merge_box->mmax) merge_box->mmax = new_box->mmax;
286
1.67M
  }
287
288
6.45M
  return LW_SUCCESS;
289
6.45M
}
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
1.28k
{
448
1.28k
  GBOX *copy = (GBOX*)lwalloc(sizeof(GBOX));
449
1.28k
  memcpy(copy, box, sizeof(GBOX));
450
1.28k
  return copy;
451
1.28k
}
452
453
void gbox_duplicate(const GBOX *original, GBOX *duplicate)
454
749
{
455
749
  assert(duplicate);
456
749
  assert(original);
457
749
  memcpy(duplicate, original, sizeof(GBOX));
458
749
}
459
460
size_t gbox_serialized_size(lwflags_t flags)
461
2.23k
{
462
2.23k
  if (FLAGS_GET_GEODETIC(flags))
463
220
    return 6 * sizeof(float);
464
2.01k
  else
465
2.01k
    return 2 * FLAGS_NDIMS(flags) * sizeof(float);
466
2.23k
}
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
3.33M
{
475
3.33M
  POINT2D xmin, ymin, xmax, ymax;
476
3.33M
  POINT2D C;
477
3.33M
  int A2_side;
478
3.33M
  double radius_A;
479
480
3.33M
  LWDEBUG(2, "lw_arc_calculate_gbox_cartesian_2d called.");
481
482
3.33M
  radius_A = lw_arc_center(A1, A2, A3, &C);
483
484
  /* Negative radius signals straight line, p1/p2/p3 are collinear */
485
3.33M
  if (radius_A < 0.0)
486
3.11k
  {
487
3.11k
        gbox->xmin = FP_MIN(A1->x, A3->x);
488
3.11k
        gbox->ymin = FP_MIN(A1->y, A3->y);
489
3.11k
        gbox->xmax = FP_MAX(A1->x, A3->x);
490
3.11k
        gbox->ymax = FP_MAX(A1->y, A3->y);
491
3.11k
      return LW_SUCCESS;
492
3.11k
  }
493
494
  /* Matched start/end points imply circle */
495
3.32M
  if ( A1->x == A3->x && A1->y == A3->y )
496
3.31M
  {
497
3.31M
    gbox->xmin = C.x - radius_A;
498
3.31M
    gbox->ymin = C.y - radius_A;
499
3.31M
    gbox->xmax = C.x + radius_A;
500
3.31M
    gbox->ymax = C.y + radius_A;
501
3.31M
    return LW_SUCCESS;
502
3.31M
  }
503
504
  /* First approximation, bounds of start/end points */
505
8.56k
    gbox->xmin = FP_MIN(A1->x, A3->x);
506
8.56k
    gbox->ymin = FP_MIN(A1->y, A3->y);
507
8.56k
    gbox->xmax = FP_MAX(A1->x, A3->x);
508
8.56k
    gbox->ymax = FP_MAX(A1->y, A3->y);
509
510
  /* Create points for the possible extrema */
511
8.56k
  xmin.x = C.x - radius_A;
512
8.56k
  xmin.y = C.y;
513
8.56k
  ymin.x = C.x;
514
8.56k
  ymin.y = C.y - radius_A;
515
8.56k
  xmax.x = C.x + radius_A;
516
8.56k
  xmax.y = C.y;
517
8.56k
  ymax.x = C.x;
518
8.56k
  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
8.56k
  A2_side = lw_segment_side(A1, A3, A2);
525
526
8.56k
  if ( A2_side == lw_segment_side(A1, A3, &xmin) )
527
4.03k
    gbox->xmin = xmin.x;
528
529
8.56k
  if ( A2_side == lw_segment_side(A1, A3, &ymin) )
530
4.06k
    gbox->ymin = ymin.y;
531
532
8.56k
  if ( A2_side == lw_segment_side(A1, A3, &xmax) )
533
4.08k
    gbox->xmax = xmax.x;
534
535
8.56k
  if ( A2_side == lw_segment_side(A1, A3, &ymax) )
536
4.23k
    gbox->ymax = ymax.y;
537
538
8.56k
  return LW_SUCCESS;
539
3.32M
}
540
541
542
static int lw_arc_calculate_gbox_cartesian(const POINT4D *p1, const POINT4D *p2, const POINT4D *p3, GBOX *gbox)
543
3.33M
{
544
3.33M
  int rv;
545
546
3.33M
  LWDEBUG(2, "lw_arc_calculate_gbox_cartesian called.");
547
548
3.33M
  rv = lw_arc_calculate_gbox_cartesian_2d((POINT2D*)p1, (POINT2D*)p2, (POINT2D*)p3, gbox);
549
3.33M
    gbox->zmin = FP_MIN(p1->z, p3->z);
550
3.33M
    gbox->mmin = FP_MIN(p1->m, p3->m);
551
3.33M
    gbox->zmax = FP_MAX(p1->z, p3->z);
552
3.33M
    gbox->mmax = FP_MAX(p1->m, p3->m);
553
3.33M
  return rv;
554
3.33M
}
555
556
static void
557
ptarray_calculate_gbox_cartesian_2d(const POINTARRAY *pa, GBOX *gbox)
558
257
{
559
257
  const POINT2D *p = getPoint2d_cp(pa, 0);
560
561
257
  gbox->xmax = gbox->xmin = p->x;
562
257
  gbox->ymax = gbox->ymin = p->y;
563
564
3.25M
  for (uint32_t i = 1; i < pa->npoints; i++)
565
3.25M
  {
566
3.25M
    p = getPoint2d_cp(pa, i);
567
3.25M
    gbox->xmin = FP_MIN(gbox->xmin, p->x);
568
3.25M
    gbox->xmax = FP_MAX(gbox->xmax, p->x);
569
3.25M
    gbox->ymin = FP_MIN(gbox->ymin, p->y);
570
3.25M
    gbox->ymax = FP_MAX(gbox->ymax, p->y);
571
3.25M
  }
572
257
}
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
237
{
578
237
  const POINT3D *p = getPoint3d_cp(pa, 0);
579
580
237
  gbox->xmax = gbox->xmin = p->x;
581
237
  gbox->ymax = gbox->ymin = p->y;
582
237
  gbox->zmax = gbox->zmin = p->z;
583
584
3.32M
  for (uint32_t i = 1; i < pa->npoints; i++)
585
3.32M
  {
586
3.32M
    p = getPoint3d_cp(pa, i);
587
3.32M
    gbox->xmin = FP_MIN(gbox->xmin, p->x);
588
3.32M
    gbox->xmax = FP_MAX(gbox->xmax, p->x);
589
3.32M
    gbox->ymin = FP_MIN(gbox->ymin, p->y);
590
3.32M
    gbox->ymax = FP_MAX(gbox->ymax, p->y);
591
3.32M
    gbox->zmin = FP_MIN(gbox->zmin, p->z);
592
3.32M
    gbox->zmax = FP_MAX(gbox->zmax, p->z);
593
3.32M
  }
594
237
}
595
596
static void
597
ptarray_calculate_gbox_cartesian_4d(const POINTARRAY *pa, GBOX *gbox)
598
135
{
599
135
  const POINT4D *p = getPoint4d_cp(pa, 0);
600
601
135
  gbox->xmax = gbox->xmin = p->x;
602
135
  gbox->ymax = gbox->ymin = p->y;
603
135
  gbox->zmax = gbox->zmin = p->z;
604
135
  gbox->mmax = gbox->mmin = p->m;
605
606
572k
  for (uint32_t i = 1; i < pa->npoints; i++)
607
572k
  {
608
572k
    p = getPoint4d_cp(pa, i);
609
572k
    gbox->xmin = FP_MIN(gbox->xmin, p->x);
610
572k
    gbox->xmax = FP_MAX(gbox->xmax, p->x);
611
572k
    gbox->ymin = FP_MIN(gbox->ymin, p->y);
612
572k
    gbox->ymax = FP_MAX(gbox->ymax, p->y);
613
572k
    gbox->zmin = FP_MIN(gbox->zmin, p->z);
614
572k
    gbox->zmax = FP_MAX(gbox->zmax, p->z);
615
572k
    gbox->mmin = FP_MIN(gbox->mmin, p->m);
616
572k
    gbox->mmax = FP_MAX(gbox->mmax, p->m);
617
572k
  }
618
135
}
619
620
int
621
ptarray_calculate_gbox_cartesian(const POINTARRAY *pa, GBOX *gbox)
622
656
{
623
656
  if (!pa || pa->npoints == 0)
624
27
    return LW_FAILURE;
625
629
  if (!gbox)
626
0
    return LW_FAILURE;
627
628
629
  int has_z = FLAGS_GET_Z(pa->flags);
629
629
  int has_m = FLAGS_GET_M(pa->flags);
630
629
  gbox->flags = lwflags(has_z, has_m, 0);
631
629
  LWDEBUGF(4, "ptarray_calculate_gbox Z: %d M: %d", has_z, has_m);
632
629
  int coordinates = 2 + has_z + has_m;
633
634
629
  switch (coordinates)
635
629
  {
636
257
  case 2:
637
257
  {
638
257
    ptarray_calculate_gbox_cartesian_2d(pa, gbox);
639
257
    break;
640
0
  }
641
237
  case 3:
642
237
  {
643
237
    if (has_z)
644
119
    {
645
119
      ptarray_calculate_gbox_cartesian_3d(pa, gbox);
646
119
    }
647
118
    else
648
118
    {
649
118
      double zmin = gbox->zmin;
650
118
      double zmax = gbox->zmax;
651
118
      ptarray_calculate_gbox_cartesian_3d(pa, gbox);
652
118
      gbox->mmin = gbox->zmin;
653
118
      gbox->mmax = gbox->zmax;
654
118
      gbox->zmin = zmin;
655
118
      gbox->zmax = zmax;
656
118
    }
657
237
    break;
658
0
  }
659
135
  default:
660
135
  {
661
135
    ptarray_calculate_gbox_cartesian_4d(pa, gbox);
662
135
    break;
663
0
  }
664
629
  }
665
629
  return LW_SUCCESS;
666
629
}
667
668
static int lwcircstring_calculate_gbox_cartesian(LWCIRCSTRING *curve, GBOX *gbox)
669
461
{
670
461
  GBOX tmp = {0};
671
461
  POINT4D p1, p2, p3;
672
461
  uint32_t i;
673
674
461
  if (!curve) return LW_FAILURE;
675
461
  if (curve->points->npoints < 3) return LW_FAILURE;
676
677
444
  tmp.flags =
678
444
      lwflags(FLAGS_GET_Z(curve->flags), FLAGS_GET_M(curve->flags), 0);
679
680
  /* Initialize */
681
444
  gbox->xmin = gbox->ymin = gbox->zmin = gbox->mmin = FLT_MAX;
682
444
  gbox->xmax = gbox->ymax = gbox->zmax = gbox->mmax = -1*FLT_MAX;
683
684
3.33M
  for ( i = 2; i < curve->points->npoints; i += 2 )
685
3.33M
  {
686
3.33M
    getPoint4d_p(curve->points, i-2, &p1);
687
3.33M
    getPoint4d_p(curve->points, i-1, &p2);
688
3.33M
    getPoint4d_p(curve->points, i, &p3);
689
690
3.33M
    if (lw_arc_calculate_gbox_cartesian(&p1, &p2, &p3, &tmp) == LW_FAILURE)
691
0
      continue;
692
693
3.33M
    gbox_merge(&tmp, gbox);
694
3.33M
  }
695
696
444
  return LW_SUCCESS;
697
461
}
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
10.1M
{
706
10.1M
  if (hpoint->w == 0.0)
707
0
    return LW_FAILURE;
708
709
10.1M
  point->x = hpoint->x / hpoint->w;
710
10.1M
  point->y = hpoint->y / hpoint->w;
711
10.1M
  point->z = hpoint->z / hpoint->w;
712
10.1M
  point->m = hpoint->m / hpoint->w;
713
10.1M
  return LW_SUCCESS;
714
10.1M
}
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
5.81M
{
724
5.81M
  NURBS_BBOX_HPOINT out;
725
5.81M
  double s = 1.0 - t;
726
727
5.81M
  out.x = s * a->x + t * b->x;
728
5.81M
  out.y = s * a->y + t * b->y;
729
5.81M
  out.z = s * a->z + t * b->z;
730
5.81M
  out.m = s * a->m + t * b->m;
731
5.81M
  out.w = s * a->w + t * b->w;
732
5.81M
  return out;
733
5.81M
}
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
3.96M
{
743
3.96M
  POINT4D point;
744
3.96M
  double weight = (curve->weights && index < curve->nweights) ? curve->weights[index] : 1.0;
745
746
3.96M
  getPoint4d_p(curve->points, index, &point);
747
3.96M
  hpoint->x = point.x * weight;
748
3.96M
  hpoint->y = point.y * weight;
749
3.96M
  hpoint->z = FLAGS_GET_Z(curve->flags) ? point.z * weight : 0.0;
750
3.96M
  hpoint->m = FLAGS_GET_M(curve->flags) ? point.m * weight : 0.0;
751
3.96M
  hpoint->w = weight;
752
3.96M
}
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
10.1M
{
761
10.1M
  gbox->xmin = FP_MIN(gbox->xmin, point->x);
762
10.1M
  gbox->xmax = FP_MAX(gbox->xmax, point->x);
763
10.1M
  gbox->ymin = FP_MIN(gbox->ymin, point->y);
764
10.1M
  gbox->ymax = FP_MAX(gbox->ymax, point->y);
765
766
10.1M
  if (FLAGS_GET_Z(gbox->flags))
767
3.93M
  {
768
3.93M
    gbox->zmin = FP_MIN(gbox->zmin, point->z);
769
3.93M
    gbox->zmax = FP_MAX(gbox->zmax, point->z);
770
3.93M
  }
771
772
10.1M
  if (FLAGS_GET_M(gbox->flags))
773
1.78M
  {
774
1.78M
    gbox->mmin = FP_MIN(gbox->mmin, point->m);
775
1.78M
    gbox->mmax = FP_MAX(gbox->mmax, point->m);
776
1.78M
  }
777
10.1M
}
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
10.1M
{
786
10.1M
  POINT4D point;
787
788
10.1M
  if (lwnurbscurve_hpoint_project(hpoint, &point) == LW_FAILURE)
789
0
    return LW_FAILURE;
790
791
10.1M
  lwnurbscurve_add_point_to_gbox(&point, gbox);
792
10.1M
  return LW_SUCCESS;
793
10.1M
}
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
4.04M
{
803
4.04M
  gbox->flags = flags;
804
4.04M
  gbox->xmin = gbox->ymin = gbox->zmin = gbox->mmin = DBL_MAX;
805
4.04M
  gbox->xmax = gbox->ymax = gbox->zmax = gbox->mmax = -DBL_MAX;
806
4.04M
}
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
2.02M
{
815
2.02M
  uint32_t i;
816
817
2.02M
  lwnurbscurve_gbox_init(gbox, flags);
818
8.13M
  for (i = 0; i < npoints; i++)
819
6.11M
  {
820
6.11M
    if (lwnurbscurve_add_hpoint_to_gbox(&points[i], gbox) == LW_FAILURE)
821
0
      return LW_FAILURE;
822
6.11M
  }
823
824
2.02M
  return LW_SUCCESS;
825
2.02M
}
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
2.02M
{
834
2.02M
  lwnurbscurve_gbox_init(gbox, flags);
835
836
2.02M
  if (lwnurbscurve_add_hpoint_to_gbox(&points[0], gbox) == LW_FAILURE ||
837
2.02M
      lwnurbscurve_add_hpoint_to_gbox(&points[degree], gbox) == LW_FAILURE)
838
0
    return LW_FAILURE;
839
840
2.02M
  return LW_SUCCESS;
841
2.02M
}
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
2.01M
{
851
2.01M
  if (next_float_down(a->xmin) != next_float_down(b->xmin) || next_float_up(a->xmax) != next_float_up(b->xmax) ||
852
2.00M
      next_float_down(a->ymin) != next_float_down(b->ymin) || next_float_up(a->ymax) != next_float_up(b->ymax))
853
13.4k
    return LW_FALSE;
854
855
2.00M
  if (FLAGS_GET_Z(a->flags) &&
856
834k
      (next_float_down(a->zmin) != next_float_down(b->zmin) || next_float_up(a->zmax) != next_float_up(b->zmax)))
857
1.76k
    return LW_FALSE;
858
859
1.99M
  if (FLAGS_GET_M(a->flags) &&
860
295k
      (next_float_down(a->mmin) != next_float_down(b->mmin) || next_float_up(a->mmax) != next_float_up(b->mmax)))
861
3.55k
    return LW_FALSE;
862
863
1.99M
  return LW_TRUE;
864
1.99M
}
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
37.5k
{
874
37.5k
  uint32_t i;
875
876
51.4k
  for (i = 0; i < npoints; i++)
877
51.4k
  {
878
51.4k
    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
37.5k
      return LW_FALSE;
880
51.4k
  }
881
882
0
  return LW_TRUE;
883
37.5k
}
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
18.7k
{
897
18.7k
  uint32_t i, r;
898
18.7k
  NURBS_BBOX_HPOINT tmp[degree + 1];
899
900
93.9k
  for (i = 0; i <= degree; i++)
901
75.1k
    tmp[i] = points[i];
902
903
18.7k
  left[0] = tmp[0];
904
18.7k
  right[degree] = tmp[degree];
905
906
75.1k
  for (r = 1; r <= degree; r++)
907
56.3k
  {
908
206k
    for (i = 0; i <= degree - r; i++)
909
150k
      tmp[i] = lwnurbscurve_hpoint_lerp(&tmp[i], &tmp[i + 1], 0.5);
910
911
56.3k
    left[r] = tmp[0];
912
56.3k
    right[degree - r] = tmp[degree - r];
913
56.3k
  }
914
18.7k
}
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
2.02M
{
934
2.02M
  GBOX hull_gbox, endpoint_gbox;
935
2.02M
  NURBS_BBOX_HPOINT left[degree + 1];
936
2.02M
  NURBS_BBOX_HPOINT right[degree + 1];
937
938
2.02M
  if (lwnurbscurve_bezier_hull_gbox(points, degree + 1, flags, &hull_gbox) == LW_FAILURE ||
939
2.02M
      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
2.02M
  if (!gbox_is_valid(&hull_gbox))
953
5.76k
  {
954
5.76k
    gbox_merge(&hull_gbox, gbox);
955
5.76k
    return LW_SUCCESS;
956
5.76k
  }
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
2.01M
  if (depth >= DBL_MANT_DIG || lwnurbscurve_gbox_same_float(&hull_gbox, &endpoint_gbox))
964
1.99M
  {
965
1.99M
    gbox_merge(&hull_gbox, gbox);
966
1.99M
    return LW_SUCCESS;
967
1.99M
  }
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
18.7k
  lwnurbscurve_bezier_split(points, degree, left, right);
972
18.7k
  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
18.7k
  if (lwnurbscurve_add_bezier_span_gbox(left, degree, flags, depth + 1, gbox) == LW_FAILURE ||
979
18.7k
      lwnurbscurve_add_bezier_span_gbox(right, degree, flags, depth + 1, gbox) == LW_FAILURE)
980
0
    return LW_FAILURE;
981
982
18.7k
  return LW_SUCCESS;
983
18.7k
}
984
985
static int lwpoint_calculate_gbox_cartesian(LWPOINT *point, GBOX *gbox)
986
39
{
987
39
  if ( ! point ) return LW_FAILURE;
988
39
  return ptarray_calculate_gbox_cartesian( point->point, gbox );
989
39
}
990
991
static int lwline_calculate_gbox_cartesian(LWLINE *line, GBOX *gbox)
992
173
{
993
173
  if ( ! line ) return LW_FAILURE;
994
173
  return ptarray_calculate_gbox_cartesian( line->points, gbox );
995
173
}
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
725
{
1007
725
  uint32_t a, b, degree, i, j, m, multiplicity, npoints, r, s;
1008
725
  int found_span = LW_FALSE;
1009
725
  double domain_max, domain_min;
1010
725
  double *knots;
1011
725
  uint32_t nknots;
1012
1013
725
  if (!curve || !curve->points || curve->points->npoints == 0)
1014
0
    return LW_FAILURE;
1015
1016
725
  degree = curve->degree;
1017
725
  npoints = curve->points->npoints;
1018
725
  knots = lwnurbscurve_get_or_generate_knots(curve, &nknots);
1019
725
  if (!knots || nknots == 0)
1020
0
    return LW_FAILURE;
1021
1022
725
  domain_min = knots[degree];
1023
725
  domain_max = knots[npoints];
1024
725
  if (domain_max <= domain_min || nknots != npoints + degree + 1)
1025
3
  {
1026
3
    lwfree(knots);
1027
3
    return LW_FAILURE;
1028
3
  }
1029
1030
722
  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
722
  {
1038
722
    NURBS_BBOX_HPOINT bezier[degree + 1];
1039
722
    NURBS_BBOX_HPOINT next_bezier[degree + 1];
1040
722
    double alphas[degree ? degree : 1];
1041
1042
3.52k
    for (i = 0; i <= degree; i++)
1043
2.79k
      lwnurbscurve_get_hpoint(curve, i, &bezier[i]);
1044
1045
722
    m = npoints + degree;
1046
722
    a = degree;
1047
722
    b = degree + 1;
1048
1049
1.98M
    while (b < m)
1050
1.98M
    {
1051
1.98M
      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
1.98M
      while (b < m && knots[b + 1] == knots[b])
1056
2.07k
        b++;
1057
1058
1.98M
      multiplicity = b - knot_block_start + 1;
1059
1.98M
      if (multiplicity < degree)
1060
957k
      {
1061
957k
        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
2.95M
        for (j = degree; j > multiplicity; j--)
1067
1.99M
        {
1068
1.99M
          double denom = knots[a + j] - knots[a];
1069
1070
1.99M
          if (denom == 0.0)
1071
0
          {
1072
0
            lwfree(knots);
1073
0
            return LW_FAILURE;
1074
0
          }
1075
1076
1.99M
          alphas[j - multiplicity - 1] = numerator / denom;
1077
1.99M
        }
1078
1079
957k
        r = degree - multiplicity;
1080
2.95M
        for (j = 1; j <= r; j++)
1081
1.99M
        {
1082
1.99M
          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
1.99M
          s = multiplicity + j;
1088
7.65M
          for (i = degree; i >= s; i--)
1089
5.66M
          {
1090
5.66M
            double alpha = alphas[i - s];
1091
5.66M
            bezier[i] = lwnurbscurve_hpoint_lerp(&bezier[i - 1], &bezier[i], alpha);
1092
5.66M
          }
1093
1094
1.99M
          if (b < m)
1095
1.99M
            next_bezier[save] = bezier[degree];
1096
1.99M
        }
1097
957k
      }
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
1.98M
      if (knots[b] > knots[a] && knots[a] >= domain_min && knots[b] <= domain_max)
1103
1.98M
      {
1104
1.98M
        if (lwnurbscurve_add_bezier_span_gbox(bezier, degree, gbox->flags, 0, gbox) ==
1105
1.98M
            LW_FAILURE)
1106
0
        {
1107
0
          lwfree(knots);
1108
0
          return LW_FAILURE;
1109
0
        }
1110
1.98M
        found_span = LW_TRUE;
1111
1.98M
      }
1112
1113
1.98M
      if (knots[b] >= domain_max || b >= m)
1114
722
        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
5.94M
      for (i = degree - FP_MIN(multiplicity, degree); i <= degree; i++)
1119
3.96M
        lwnurbscurve_get_hpoint(curve, b - degree + i, &next_bezier[i]);
1120
1121
7.94M
      for (i = 0; i <= degree; i++)
1122
5.96M
        bezier[i] = next_bezier[i];
1123
1124
1.98M
      a = b;
1125
1.98M
      b++;
1126
1.98M
    }
1127
722
  }
1128
1129
722
  lwfree(knots);
1130
1131
722
  return found_span ? LW_SUCCESS : LW_FAILURE;
1132
722
}
1133
1134
static int lwtriangle_calculate_gbox_cartesian(LWTRIANGLE *triangle, GBOX *gbox)
1135
285
{
1136
285
  if ( ! triangle ) return LW_FAILURE;
1137
285
  return ptarray_calculate_gbox_cartesian( triangle->points, gbox );
1138
285
}
1139
1140
static int lwpoly_calculate_gbox_cartesian(LWPOLY *poly, GBOX *gbox)
1141
186
{
1142
186
  if ( ! poly ) return LW_FAILURE;
1143
186
  if ( poly->nrings == 0 ) return LW_FAILURE;
1144
  /* Just need to check outer ring */
1145
159
  return ptarray_calculate_gbox_cartesian( poly->rings[0], gbox );
1146
186
}
1147
1148
static int lwcollection_calculate_gbox_cartesian(LWCOLLECTION *coll, GBOX *gbox)
1149
146
{
1150
146
  GBOX subbox = {0};
1151
146
  uint32_t i;
1152
146
  int result = LW_FAILURE;
1153
146
  int first = LW_TRUE;
1154
146
  assert(coll);
1155
146
  if ( (coll->ngeoms == 0) || !gbox)
1156
73
    return LW_FAILURE;
1157
1158
73
  subbox.flags = coll->flags;
1159
1160
185
  for ( i = 0; i < coll->ngeoms; i++ )
1161
112
  {
1162
112
    if ( lwgeom_calculate_gbox_cartesian((LWGEOM*)(coll->geoms[i]), &subbox) == LW_SUCCESS )
1163
85
    {
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
85
      if ( first )
1169
64
      {
1170
64
        gbox_duplicate(&subbox, gbox);
1171
64
        first = LW_FALSE;
1172
64
      }
1173
21
      else
1174
21
      {
1175
21
        gbox_merge(&subbox, gbox);
1176
21
      }
1177
85
      result = LW_SUCCESS;
1178
85
    }
1179
112
  }
1180
73
  return result;
1181
146
}
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
2.01k
{
1197
2.01k
  if ( ! lwgeom ) return LW_FAILURE;
1198
2.01k
  LWDEBUGF(4, "lwgeom_calculate_gbox got type (%d) - %s", lwgeom->type, lwtype_name(lwgeom->type));
1199
1200
2.01k
  switch (lwgeom->type)
1201
2.01k
  {
1202
39
  case POINTTYPE:
1203
39
    return lwpoint_calculate_gbox_cartesian((LWPOINT *)lwgeom, gbox);
1204
173
  case LINETYPE:
1205
173
    return lwline_calculate_gbox_cartesian((LWLINE *)lwgeom, gbox);
1206
461
  case CIRCSTRINGTYPE:
1207
461
    return lwcircstring_calculate_gbox_cartesian((LWCIRCSTRING *)lwgeom, gbox);
1208
186
  case POLYGONTYPE:
1209
186
    return lwpoly_calculate_gbox_cartesian((LWPOLY *)lwgeom, gbox);
1210
285
  case TRIANGLETYPE:
1211
285
    return lwtriangle_calculate_gbox_cartesian((LWTRIANGLE *)lwgeom, gbox);
1212
38
  case COMPOUNDTYPE:
1213
56
  case CURVEPOLYTYPE:
1214
68
  case MULTIPOINTTYPE:
1215
76
  case MULTILINETYPE:
1216
97
  case MULTICURVETYPE:
1217
105
  case MULTIPOLYGONTYPE:
1218
113
  case MULTISURFACETYPE:
1219
119
  case POLYHEDRALSURFACETYPE:
1220
124
  case TINTYPE:
1221
146
  case COLLECTIONTYPE:
1222
146
    return lwcollection_calculate_gbox_cartesian((LWCOLLECTION *)lwgeom, gbox);
1223
725
  case NURBSCURVETYPE:
1224
725
    return lwnurbscurve_calculate_gbox_cartesian((const LWNURBSCURVE *)lwgeom, gbox);
1225
2.01k
  }
1226
  /* Never get here, please. */
1227
0
  lwerror("unsupported type (%d) - %s", lwgeom->type, lwtype_name(lwgeom->type));
1228
0
  return LW_FAILURE;
1229
2.01k
}
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
}