/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 | } |