Coverage Report

Created: 2026-08-13 07:11

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/postgis/liblwgeom/lwgeodetic.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
 * Copyright 2009 David Skea <David.Skea@gov.bc.ca>
23
 *
24
 **********************************************************************/
25
26
27
#include "liblwgeom_internal.h"
28
#include "lwgeodetic.h"
29
#include "lwgeom_log.h"
30
31
/**
32
* For testing geodetic bounding box, we have a magic global variable.
33
* When this is true (when the cunit tests set it), use the slow, but
34
* guaranteed correct, algorithm. Otherwise use the regular one.
35
*/
36
int gbox_geocentric_slow = LW_FALSE;
37
38
/**
39
* Utility function for ptarray_contains_point_sphere()
40
*/
41
static int
42
point3d_equals(const POINT3D *p1, const POINT3D *p2)
43
0
{
44
0
  return FP_EQUALS(p1->x, p2->x) && FP_EQUALS(p1->y, p2->y) && FP_EQUALS(p1->z, p2->z);
45
0
}
46
47
/**
48
* Convert a longitude to the range of -PI,PI
49
*/
50
double longitude_radians_normalize(double lon)
51
0
{
52
0
  if ( lon == -1.0 * M_PI )
53
0
    return M_PI;
54
0
  if ( lon == -2.0 * M_PI )
55
0
    return 0.0;
56
57
0
  if ( lon > 2.0 * M_PI )
58
0
    lon = remainder(lon, 2.0 * M_PI);
59
60
0
  if ( lon < -2.0 * M_PI )
61
0
    lon = remainder(lon, -2.0 * M_PI);
62
63
0
  if ( lon > M_PI )
64
0
    lon = -2.0 * M_PI + lon;
65
66
0
  if ( lon < -1.0 * M_PI )
67
0
    lon = 2.0 * M_PI + lon;
68
69
0
  if ( lon == -2.0 * M_PI )
70
0
    lon *= -1.0;
71
72
0
  return lon;
73
0
}
74
75
/**
76
* Convert a latitude to the range of -PI/2,PI/2
77
*/
78
double latitude_radians_normalize(double lat)
79
0
{
80
81
0
  if ( lat > 2.0 * M_PI )
82
0
    lat = remainder(lat, 2.0 * M_PI);
83
84
0
  if ( lat < -2.0 * M_PI )
85
0
    lat = remainder(lat, -2.0 * M_PI);
86
87
0
  if ( lat > M_PI )
88
0
    lat = M_PI - lat;
89
90
0
  if ( lat < -1.0 * M_PI )
91
0
    lat = -1.0 * M_PI - lat;
92
93
0
  if ( lat > M_PI_2 )
94
0
    lat = M_PI - lat;
95
96
0
  if ( lat < -1.0 * M_PI_2 )
97
0
    lat = -1.0 * M_PI - lat;
98
99
0
  return lat;
100
0
}
101
102
/**
103
* Convert a longitude to the range of -180,180
104
* @param lon longitude in degrees
105
*/
106
double longitude_degrees_normalize(double lon)
107
0
{
108
0
  if ( lon > 360.0 )
109
0
    lon = remainder(lon, 360.0);
110
111
0
  if ( lon < -360.0 )
112
0
    lon = remainder(lon, -360.0);
113
114
0
  if ( lon > 180.0 )
115
0
    lon = -360.0 + lon;
116
117
0
  if ( lon < -180.0 )
118
0
    lon = 360 + lon;
119
120
0
  if ( lon == -180.0 )
121
0
    return 180.0;
122
123
0
  if ( lon == -360.0 )
124
0
    return 0.0;
125
126
0
  return lon;
127
0
}
128
129
/**
130
* Convert a latitude to the range of -90,90
131
* @param lat latitude in degrees
132
*/
133
double latitude_degrees_normalize(double lat)
134
0
{
135
136
0
  if ( lat > 360.0 )
137
0
    lat = remainder(lat, 360.0);
138
139
0
  if ( lat < -360.0 )
140
0
    lat = remainder(lat, -360.0);
141
142
0
  if ( lat > 180.0 )
143
0
    lat = 180.0 - lat;
144
145
0
  if ( lat < -180.0 )
146
0
    lat = -180.0 - lat;
147
148
0
  if ( lat > 90.0 )
149
0
    lat = 180.0 - lat;
150
151
0
  if ( lat < -90.0 )
152
0
    lat = -180.0 - lat;
153
154
0
  return lat;
155
0
}
156
157
/**
158
* Shift a point around by a number of radians
159
*/
160
void point_shift(GEOGRAPHIC_POINT *p, double shift)
161
0
{
162
0
  double lon = p->lon + shift;
163
0
  if ( lon > M_PI )
164
0
    p->lon = -1.0 * M_PI + (lon - M_PI);
165
0
  else
166
0
    p->lon = lon;
167
0
  return;
168
0
}
169
170
int geographic_point_equals(const GEOGRAPHIC_POINT *g1, const GEOGRAPHIC_POINT *g2)
171
0
{
172
0
  return FP_EQUALS(g1->lat, g2->lat) && FP_EQUALS(g1->lon, g2->lon);
173
0
}
174
175
/**
176
* Initialize a geographic point
177
* @param lon longitude in degrees
178
* @param lat latitude in degrees
179
*/
180
void geographic_point_init(double lon, double lat, GEOGRAPHIC_POINT *g)
181
0
{
182
0
  g->lat = latitude_radians_normalize(deg2rad(lat));
183
0
  g->lon = longitude_radians_normalize(deg2rad(lon));
184
0
}
185
186
/** Returns the angular height (latitudinal span) of the box in radians */
187
double
188
gbox_angular_height(const GBOX* gbox)
189
0
{
190
0
  double d[6];
191
0
  int i;
192
0
  double zmin = FLT_MAX;
193
0
  double zmax = -1 * FLT_MAX;
194
0
  POINT3D pt;
195
196
  /* Take a copy of the box corners so we can treat them as a list */
197
  /* Elements are xmin, xmax, ymin, ymax, zmin, zmax */
198
0
  memcpy(d, &(gbox->xmin), 6*sizeof(double));
199
200
  /* Generate all 8 corner vectors of the box */
201
0
  for ( i = 0; i < 8; i++ )
202
0
  {
203
0
    pt.x = d[i / 4];
204
0
    pt.y = d[2 + (i % 4) / 2];
205
0
    pt.z = d[4 + (i % 2)];
206
0
    normalize(&pt);
207
0
    if ( pt.z < zmin ) zmin = pt.z;
208
0
    if ( pt.z > zmax ) zmax = pt.z;
209
0
  }
210
0
  return asin(zmax) - asin(zmin);
211
0
}
212
213
/** Returns the angular width (longitudinal span) of the box in radians */
214
double
215
gbox_angular_width(const GBOX* gbox)
216
0
{
217
0
  double d[6];
218
0
  int i, j;
219
0
  POINT3D pt[3];
220
0
  double maxangle;
221
0
  double magnitude;
222
223
  /* Take a copy of the box corners so we can treat them as a list */
224
  /* Elements are xmin, xmax, ymin, ymax, zmin, zmax */
225
0
  memcpy(d, &(gbox->xmin), 6*sizeof(double));
226
227
  /* Start with the bottom corner */
228
0
  pt[0].x = gbox->xmin;
229
0
  pt[0].y = gbox->ymin;
230
0
  magnitude = sqrt(pt[0].x*pt[0].x + pt[0].y*pt[0].y);
231
0
  pt[0].x /= magnitude;
232
0
  pt[0].y /= magnitude;
233
234
  /* Generate all 8 corner vectors of the box */
235
  /* Find the vector furthest from our seed vector */
236
0
  for ( j = 0; j < 2; j++ )
237
0
  {
238
0
    maxangle = -1 * FLT_MAX;
239
0
    for ( i = 0; i < 4; i++ )
240
0
    {
241
0
      double angle, dotprod;
242
0
      POINT3D pt_n;
243
244
0
      pt_n.x = d[i / 2];
245
0
      pt_n.y = d[2 + (i % 2)];
246
0
      magnitude = sqrt(pt_n.x*pt_n.x + pt_n.y*pt_n.y);
247
0
      pt_n.x /= magnitude;
248
0
      pt_n.y /= magnitude;
249
0
      pt_n.z = 0.0;
250
251
0
      dotprod = pt_n.x*pt[j].x + pt_n.y*pt[j].y;
252
0
      angle = acos(dotprod > 1.0 ? 1.0 : dotprod);
253
0
      if ( angle > maxangle )
254
0
      {
255
0
        pt[j+1] = pt_n;
256
0
        maxangle = angle;
257
0
      }
258
0
    }
259
0
  }
260
261
  /* Return the distance between the two furthest vectors */
262
0
  return maxangle;
263
0
}
264
265
/** Computes the average(ish) center of the box and returns success. */
266
int
267
gbox_centroid(const GBOX* gbox, POINT2D* out)
268
0
{
269
0
  double d[6];
270
0
  GEOGRAPHIC_POINT g;
271
0
  POINT3D pt;
272
0
  int i;
273
274
  /* Take a copy of the box corners so we can treat them as a list */
275
  /* Elements are xmin, xmax, ymin, ymax, zmin, zmax */
276
0
  memcpy(d, &(gbox->xmin), 6*sizeof(double));
277
278
  /* Zero out our return vector */
279
0
  pt.x = pt.y = pt.z = 0.0;
280
281
0
  for ( i = 0; i < 8; i++ )
282
0
  {
283
0
    POINT3D pt_n;
284
285
0
    pt_n.x = d[i / 4];
286
0
    pt_n.y = d[2 + ((i % 4) / 2)];
287
0
    pt_n.z = d[4 + (i % 2)];
288
0
    normalize(&pt_n);
289
290
0
    pt.x += pt_n.x;
291
0
    pt.y += pt_n.y;
292
0
    pt.z += pt_n.z;
293
0
  }
294
295
0
  pt.x /= 8.0;
296
0
  pt.y /= 8.0;
297
0
  pt.z /= 8.0;
298
0
  normalize(&pt);
299
300
0
  cart2geog(&pt, &g);
301
0
  out->x = longitude_degrees_normalize(rad2deg(g.lon));
302
0
  out->y = latitude_degrees_normalize(rad2deg(g.lat));
303
304
0
  return LW_SUCCESS;
305
0
}
306
307
/**
308
* Check to see if this geocentric gbox is wrapped around a pole.
309
* Only makes sense if this gbox originated from a polygon, as it's assuming
310
* the box is generated from external edges and there's an "interior" which
311
* contains the pole.
312
*
313
* This function is overdetermined, for very large polygons it might add an
314
* unwarranted pole. STILL NEEDS WORK!
315
*/
316
static int gbox_check_poles(GBOX *gbox)
317
0
{
318
0
  int rv = LW_FALSE;
319
#if POSTGIS_DEBUG_LEVEL >= 4
320
  char *gbox_str = gbox_to_string(gbox);
321
  LWDEBUG(4, "checking poles");
322
  LWDEBUGF(4, "gbox %s", gbox_str);
323
  lwfree(gbox_str);
324
#endif
325
  /* Z axis */
326
0
  if (gbox->xmin < 0.0 && gbox->xmax > 0.0 &&
327
0
      gbox->ymin < 0.0 && gbox->ymax > 0.0)
328
0
  {
329
    /* Extrema lean positive */
330
0
    if ((gbox->zmin > 0.0) && (gbox->zmax > 0.0))
331
0
    {
332
0
      LWDEBUG(4, "enclosed positive z axis");
333
0
      gbox->zmax = 1.0;
334
0
    }
335
    /* Extrema lean negative */
336
0
    else if ((gbox->zmin < 0.0) && (gbox->zmax < 0.0))
337
0
    {
338
0
      LWDEBUG(4, "enclosed negative z axis");
339
0
      gbox->zmin = -1.0;
340
0
    }
341
    /* Extrema both sides! */
342
0
    else
343
0
    {
344
0
      LWDEBUG(4, "enclosed both z axes");
345
0
      gbox->zmin = -1.0;
346
0
      gbox->zmax = 1.0;
347
0
    }
348
0
    rv = LW_TRUE;
349
0
  }
350
351
  /* Y axis */
352
0
  if (gbox->xmin < 0.0 && gbox->xmax > 0.0 &&
353
0
      gbox->zmin < 0.0 && gbox->zmax > 0.0)
354
0
  {
355
0
    if ((gbox->ymin > 0.0) && (gbox->ymax > 0.0))
356
0
    {
357
0
      LWDEBUG(4, "enclosed positive y axis");
358
0
      gbox->ymax = 1.0;
359
0
    }
360
0
    else if ((gbox->ymin < 0.0) && (gbox->ymax < 0.0))
361
0
    {
362
0
      LWDEBUG(4, "enclosed negative y axis");
363
0
      gbox->ymin = -1.0;
364
0
    }
365
0
    else
366
0
    {
367
0
      LWDEBUG(4, "enclosed both y axes");
368
0
      gbox->ymax = 1.0;
369
0
      gbox->ymin = -1.0;
370
0
    }
371
0
    rv = LW_TRUE;
372
0
  }
373
374
  /* X axis */
375
0
  if (gbox->ymin < 0.0 && gbox->ymax > 0.0 &&
376
0
      gbox->zmin < 0.0 && gbox->zmax > 0.0)
377
0
  {
378
0
    if ((gbox->xmin > 0.0) && (gbox->xmax > 0.0))
379
0
    {
380
0
      LWDEBUG(4, "enclosed positive x axis");
381
0
      gbox->xmax = 1.0;
382
0
    }
383
0
    else if ((gbox->xmin < 0.0) && (gbox->xmax < 0.0))
384
0
    {
385
0
      LWDEBUG(4, "enclosed negative x axis");
386
0
      gbox->xmin = -1.0;
387
0
    }
388
0
    else
389
0
    {
390
0
      LWDEBUG(4, "enclosed both x axes");
391
0
      gbox->xmax = 1.0;
392
0
      gbox->xmin = -1.0;
393
0
    }
394
395
0
    rv = LW_TRUE;
396
0
  }
397
398
0
  return rv;
399
0
}
400
401
/**
402
* Convert spherical coordinates to cartesian coordinates on unit sphere
403
*/
404
void geog2cart(const GEOGRAPHIC_POINT *g, POINT3D *p)
405
0
{
406
0
  p->x = cos(g->lat) * cos(g->lon);
407
0
  p->y = cos(g->lat) * sin(g->lon);
408
0
  p->z = sin(g->lat);
409
0
}
410
411
/**
412
* Convert cartesian coordinates on unit sphere to spherical coordinates
413
*/
414
void cart2geog(const POINT3D *p, GEOGRAPHIC_POINT *g)
415
0
{
416
0
  g->lon = atan2(p->y, p->x);
417
0
  g->lat = asin(p->z);
418
0
}
419
420
/**
421
* Convert lon/lat coordinates to cartesian coordinates on unit sphere
422
*/
423
void ll2cart(const POINT2D *g, POINT3D *p)
424
0
{
425
0
  double x_rad = M_PI * g->x / 180.0;
426
0
  double y_rad = M_PI * g->y / 180.0;
427
0
  double cos_y_rad = cos(y_rad);
428
0
  p->x = cos_y_rad * cos(x_rad);
429
0
  p->y = cos_y_rad * sin(x_rad);
430
0
  p->z = sin(y_rad);
431
0
}
432
433
/**
434
* Convert cartesian coordinates on unit sphere to lon/lat coordinates
435
static void cart2ll(const POINT3D *p, POINT2D *g)
436
{
437
  g->x = longitude_degrees_normalize(180.0 * atan2(p->y, p->x) / M_PI);
438
  g->y = latitude_degrees_normalize(180.0 * asin(p->z) / M_PI);
439
}
440
*/
441
442
/**
443
* Calculate the dot product of two unit vectors
444
* (-1 == opposite, 0 == orthogonal, 1 == identical)
445
*/
446
static double dot_product(const POINT3D *p1, const POINT3D *p2)
447
0
{
448
0
  return (p1->x*p2->x) + (p1->y*p2->y) + (p1->z*p2->z);
449
0
}
450
451
/**
452
* Calculate the cross product of two vectors
453
*/
454
static void cross_product(const POINT3D *a, const POINT3D *b, POINT3D *n)
455
0
{
456
0
  n->x = a->y * b->z - a->z * b->y;
457
0
  n->y = a->z * b->x - a->x * b->z;
458
0
  n->z = a->x * b->y - a->y * b->x;
459
0
  return;
460
0
}
461
462
/**
463
* Calculate the sum of two vectors
464
*/
465
void vector_sum(const POINT3D *a, const POINT3D *b, POINT3D *n)
466
0
{
467
0
  n->x = a->x + b->x;
468
0
  n->y = a->y + b->y;
469
0
  n->z = a->z + b->z;
470
0
  return;
471
0
}
472
473
/**
474
* Calculate the difference of two vectors
475
*/
476
static void vector_difference(const POINT3D *a, const POINT3D *b, POINT3D *n)
477
0
{
478
0
  n->x = a->x - b->x;
479
0
  n->y = a->y - b->y;
480
0
  n->z = a->z - b->z;
481
0
  return;
482
0
}
483
484
/**
485
* Scale a vector out by a factor
486
*/
487
void vector_scale(POINT3D *n, double scale)
488
0
{
489
0
  n->x *= scale;
490
0
  n->y *= scale;
491
0
  n->z *= scale;
492
0
  return;
493
0
}
494
495
/*
496
* static inline double vector_magnitude(const POINT3D* v)
497
* {
498
* return sqrt(v->x*v->x + v->y*v->y + v->z*v->z);
499
* }
500
*/
501
502
/**
503
* Angle between two unit vectors
504
*/
505
double vector_angle(const POINT3D* v1, const POINT3D* v2)
506
0
{
507
0
  POINT3D v3, normal;
508
0
  double angle, x, y;
509
510
0
  cross_product(v1, v2, &normal);
511
0
  normalize(&normal);
512
0
  cross_product(&normal, v1, &v3);
513
514
0
  x = dot_product(v1, v2);
515
0
  y = dot_product(v2, &v3);
516
517
0
  angle = atan2(y, x);
518
0
  return angle;
519
0
}
520
521
/**
522
* Normalize to a unit vector.
523
*/
524
static void normalize2d(POINT2D *p)
525
0
{
526
0
  double d = sqrt(p->x*p->x + p->y*p->y);
527
0
  if (FP_IS_ZERO(d))
528
0
  {
529
0
    p->x = p->y = 0.0;
530
0
    return;
531
0
  }
532
0
  p->x = p->x / d;
533
0
  p->y = p->y / d;
534
0
  return;
535
0
}
536
537
/**
538
* Calculates the unit normal to two vectors, trying to avoid
539
* problems with over-narrow or over-wide cases.
540
*/
541
void unit_normal(const POINT3D *P1, const POINT3D *P2, POINT3D *normal)
542
0
{
543
0
  double p_dot = dot_product(P1, P2);
544
0
  POINT3D P3;
545
546
  /* If edge is really large, calculate a narrower equivalent angle A1/A3. */
547
0
  if ( p_dot < 0 )
548
0
  {
549
0
    vector_sum(P1, P2, &P3);
550
0
    normalize(&P3);
551
0
  }
552
  /* If edge is narrow, calculate a wider equivalent angle A1/A3. */
553
0
  else if ( p_dot > 0.95 )
554
0
  {
555
0
    vector_difference(P2, P1, &P3);
556
0
    normalize(&P3);
557
0
  }
558
  /* Just keep the current angle in A1/A3. */
559
0
  else
560
0
  {
561
0
    P3 = *P2;
562
0
  }
563
564
  /* Normals to the A-plane and B-plane */
565
0
  cross_product(P1, &P3, normal);
566
0
  normalize(normal);
567
0
}
568
569
/**
570
* Rotates v1 through an angle (in radians) within the plane defined by v1/v2, returns
571
* the rotated vector in n.
572
*/
573
void vector_rotate(const POINT3D* v1, const POINT3D* v2, double angle, POINT3D* n)
574
0
{
575
0
  POINT3D u;
576
0
  double cos_a = cos(angle);
577
0
  double sin_a = sin(angle);
578
0
  double uxuy, uyuz, uxuz;
579
0
  double ux2, uy2, uz2;
580
0
  double rxx, rxy, rxz, ryx, ryy, ryz, rzx, rzy, rzz;
581
582
  /* Need a unit vector normal to rotate around */
583
0
  unit_normal(v1, v2, &u);
584
585
0
  uxuy = u.x * u.y;
586
0
  uxuz = u.x * u.z;
587
0
  uyuz = u.y * u.z;
588
589
0
  ux2 = u.x * u.x;
590
0
  uy2 = u.y * u.y;
591
0
  uz2 = u.z * u.z;
592
593
0
  rxx = cos_a + ux2 * (1 - cos_a);
594
0
  rxy = uxuy * (1 - cos_a) - u.z * sin_a;
595
0
  rxz = uxuz * (1 - cos_a) + u.y * sin_a;
596
597
0
  ryx = uxuy * (1 - cos_a) + u.z * sin_a;
598
0
  ryy = cos_a + uy2 * (1 - cos_a);
599
0
  ryz = uyuz * (1 - cos_a) - u.x * sin_a;
600
601
0
  rzx = uxuz * (1 - cos_a) - u.y * sin_a;
602
0
  rzy = uyuz * (1 - cos_a) + u.x * sin_a;
603
0
  rzz = cos_a + uz2 * (1 - cos_a);
604
605
0
  n->x = rxx * v1->x + rxy * v1->y + rxz * v1->z;
606
0
  n->y = ryx * v1->x + ryy * v1->y + ryz * v1->z;
607
0
  n->z = rzx * v1->x + rzy * v1->y + rzz * v1->z;
608
609
0
  normalize(n);
610
0
}
611
612
/**
613
* Normalize to a unit vector.
614
*/
615
void normalize(POINT3D *p)
616
0
{
617
0
  double d = sqrt(p->x*p->x + p->y*p->y + p->z*p->z);
618
0
  if (FP_IS_ZERO(d))
619
0
  {
620
0
    p->x = p->y = p->z = 0.0;
621
0
    return;
622
0
  }
623
0
  p->x = p->x / d;
624
0
  p->y = p->y / d;
625
0
  p->z = p->z / d;
626
0
  return;
627
0
}
628
629
630
/**
631
* Computes the cross product of two vectors using their lat, lng representations.
632
* Good even for small distances between p and q.
633
*/
634
void robust_cross_product(const GEOGRAPHIC_POINT *p, const GEOGRAPHIC_POINT *q, POINT3D *a)
635
0
{
636
0
  double lon_qpp = (q->lon + p->lon) / -2.0;
637
0
  double lon_qmp = (q->lon - p->lon) / 2.0;
638
0
  double sin_p_lat_minus_q_lat = sin(p->lat-q->lat);
639
0
  double sin_p_lat_plus_q_lat = sin(p->lat+q->lat);
640
0
  double sin_lon_qpp = sin(lon_qpp);
641
0
  double sin_lon_qmp = sin(lon_qmp);
642
0
  double cos_lon_qpp = cos(lon_qpp);
643
0
  double cos_lon_qmp = cos(lon_qmp);
644
0
  a->x = sin_p_lat_minus_q_lat * sin_lon_qpp * cos_lon_qmp -
645
0
         sin_p_lat_plus_q_lat * cos_lon_qpp * sin_lon_qmp;
646
0
  a->y = sin_p_lat_minus_q_lat * cos_lon_qpp * cos_lon_qmp +
647
0
         sin_p_lat_plus_q_lat * sin_lon_qpp * sin_lon_qmp;
648
0
  a->z = cos(p->lat) * cos(q->lat) * sin(q->lon-p->lon);
649
0
}
650
651
void x_to_z(POINT3D *p)
652
0
{
653
0
  double tmp = p->z;
654
0
  p->z = p->x;
655
0
  p->x = tmp;
656
0
}
657
658
void y_to_z(POINT3D *p)
659
0
{
660
0
  double tmp = p->z;
661
0
  p->z = p->y;
662
0
  p->y = tmp;
663
0
}
664
665
666
int crosses_dateline(const GEOGRAPHIC_POINT *s, const GEOGRAPHIC_POINT *e)
667
0
{
668
0
  double sign_s = SIGNUM(s->lon);
669
0
  double sign_e = SIGNUM(e->lon);
670
0
  double ss = fabs(s->lon);
671
0
  double ee = fabs(e->lon);
672
0
  if ( sign_s == sign_e )
673
0
  {
674
0
    return LW_FALSE;
675
0
  }
676
0
  else
677
0
  {
678
0
    double dl = ss + ee;
679
0
    if ( dl < M_PI )
680
0
      return LW_FALSE;
681
0
    else if ( FP_EQUALS(dl, M_PI) )
682
0
      return LW_FALSE;
683
0
    else
684
0
      return LW_TRUE;
685
0
  }
686
0
}
687
688
/**
689
* Returns -1 if the point is to the left of the plane formed
690
* by the edge, 1 if the point is to the right, and 0 if the
691
* point is on the plane.
692
*/
693
static int
694
edge_point_side(const GEOGRAPHIC_EDGE *e, const GEOGRAPHIC_POINT *p)
695
0
{
696
0
  POINT3D normal, pt;
697
0
  double w;
698
  /* Normal to the plane defined by e */
699
0
  robust_cross_product(&(e->start), &(e->end), &normal);
700
0
  normalize(&normal);
701
0
  geog2cart(p, &pt);
702
  /* We expect the dot product of with normal with any vector in the plane to be zero */
703
0
  w = dot_product(&normal, &pt);
704
0
  LWDEBUGF(4,"dot product %.9g",w);
705
0
  if ( FP_IS_ZERO(w) )
706
0
  {
707
0
    LWDEBUG(4, "point is on plane (dot product is zero)");
708
0
    return 0;
709
0
  }
710
711
0
  if ( w < 0 )
712
0
    return -1;
713
0
  else
714
0
    return 1;
715
0
}
716
717
/**
718
* Returns true if the point p is on the great circle plane.
719
* Forms the scalar triple product of A,B,p and if the volume of the
720
* resulting parallelepiped is near zero the point p is on the
721
* great circle plane.
722
*/
723
int edge_point_on_plane(const GEOGRAPHIC_EDGE *e, const GEOGRAPHIC_POINT *p)
724
0
{
725
0
  int side = edge_point_side(e, p);
726
0
  if ( side == 0 )
727
0
    return LW_TRUE;
728
729
0
  return LW_FALSE;
730
0
}
731
732
/**
733
* Returns true if the point p is inside the cone defined by the
734
* two ends of the edge e.
735
*/
736
int edge_point_in_cone(const GEOGRAPHIC_EDGE *e, const GEOGRAPHIC_POINT *p)
737
0
{
738
0
  POINT3D vcp, vs, ve, vp;
739
0
  double vs_dot_vcp, vp_dot_vcp;
740
0
  geog2cart(&(e->start), &vs);
741
0
  geog2cart(&(e->end), &ve);
742
  /* Antipodal case, everything is inside. */
743
0
  if ( vs.x == -1.0 * ve.x && vs.y == -1.0 * ve.y && vs.z == -1.0 * ve.z )
744
0
    return LW_TRUE;
745
0
  geog2cart(p, &vp);
746
  /* The normalized sum bisects the angle between start and end. */
747
0
  vector_sum(&vs, &ve, &vcp);
748
0
  normalize(&vcp);
749
  /* The projection of start onto the center defines the minimum similarity */
750
0
  vs_dot_vcp = dot_product(&vs, &vcp);
751
0
  LWDEBUGF(4,"vs_dot_vcp %.19g",vs_dot_vcp);
752
  /* The projection of candidate p onto the center */
753
0
  vp_dot_vcp = dot_product(&vp, &vcp);
754
0
  LWDEBUGF(4,"vp_dot_vcp %.19g",vp_dot_vcp);
755
  /* If p is more similar than start then p is inside the cone */
756
0
  LWDEBUGF(4,"fabs(vp_dot_vcp - vs_dot_vcp) %.39g",fabs(vp_dot_vcp - vs_dot_vcp));
757
758
  /*
759
  ** We want to test that vp_dot_vcp is >= vs_dot_vcp but there are
760
  ** numerical stability issues for values that are very very nearly
761
  ** equal. Unfortunately there are also values of vp_dot_vcp that are legitimately
762
  ** very close to but still less than vs_dot_vcp which we also need to catch.
763
  ** The tolerance of 10-17 seems to do the trick on 32-bit and 64-bit architectures,
764
  ** for the test cases here.
765
  ** However, tuning the tolerance value feels like a dangerous hack.
766
  ** Fundamentally, the problem is that this test is so sensitive.
767
  */
768
769
  /* 1.1102230246251565404236316680908203125e-16 */
770
771
0
  if ( vp_dot_vcp > vs_dot_vcp || fabs(vp_dot_vcp - vs_dot_vcp) < 2e-16 )
772
0
  {
773
0
    LWDEBUG(4, "point is in cone");
774
0
    return LW_TRUE;
775
0
  }
776
0
  LWDEBUG(4, "point is not in cone");
777
0
  return LW_FALSE;
778
0
}
779
780
/**
781
* True if the longitude of p is within the range of the longitude of the ends of e
782
*/
783
int edge_contains_coplanar_point(const GEOGRAPHIC_EDGE *e, const GEOGRAPHIC_POINT *p)
784
0
{
785
0
  GEOGRAPHIC_EDGE g;
786
0
  GEOGRAPHIC_POINT q;
787
0
  double slon = fabs((e->start).lon) + fabs((e->end).lon);
788
0
  double dlon = fabs(fabs((e->start).lon) - fabs((e->end).lon));
789
0
  double slat = (e->start).lat + (e->end).lat;
790
791
0
  LWDEBUGF(4, "e.start == GPOINT(%.6g %.6g) ", (e->start).lat, (e->start).lon);
792
0
  LWDEBUGF(4, "e.end == GPOINT(%.6g %.6g) ", (e->end).lat, (e->end).lon);
793
0
  LWDEBUGF(4, "p == GPOINT(%.6g %.6g) ", p->lat, p->lon);
794
795
  /* Copy values into working registers */
796
0
  g = *e;
797
0
  q = *p;
798
799
  /* Vertical plane, we need to do this calculation in latitude */
800
0
  if ( FP_EQUALS( g.start.lon, g.end.lon ) )
801
0
  {
802
0
    LWDEBUG(4, "vertical plane, we need to do this calculation in latitude");
803
    /* Supposed to be co-planar... */
804
0
    if ( ! FP_EQUALS( q.lon, g.start.lon ) )
805
0
      return LW_FALSE;
806
807
0
    if ( ( g.start.lat <= q.lat && q.lat <= g.end.lat ) ||
808
0
         ( g.end.lat <= q.lat && q.lat <= g.start.lat ) )
809
0
    {
810
0
      return LW_TRUE;
811
0
    }
812
0
    else
813
0
    {
814
0
      return LW_FALSE;
815
0
    }
816
0
  }
817
818
  /* Over the pole, we need normalize latitude and do this calculation in latitude */
819
0
  if ( FP_EQUALS( slon, M_PI ) && ( SIGNUM(g.start.lon) != SIGNUM(g.end.lon) || FP_EQUALS(dlon, M_PI) ) )
820
0
  {
821
0
    LWDEBUG(4, "over the pole...");
822
    /* Antipodal, everything (or nothing?) is inside */
823
0
    if ( FP_EQUALS( slat, 0.0 ) )
824
0
      return LW_TRUE;
825
826
    /* Point *is* the north pole */
827
0
    if ( slat > 0.0 && FP_EQUALS(q.lat, M_PI_2 ) )
828
0
      return LW_TRUE;
829
830
    /* Point *is* the south pole */
831
0
    if ( slat < 0.0 && FP_EQUALS(q.lat, -1.0 * M_PI_2) )
832
0
      return LW_TRUE;
833
834
0
    LWDEBUG(4, "coplanar?...");
835
836
    /* Supposed to be co-planar... */
837
0
    if ( ! FP_EQUALS( q.lon, g.start.lon ) )
838
0
      return LW_FALSE;
839
840
0
    LWDEBUG(4, "north or south?...");
841
842
    /* Over north pole, test based on south pole */
843
0
    if ( slat > 0.0 )
844
0
    {
845
0
      LWDEBUG(4, "over the north pole...");
846
0
      if ( q.lat > FP_MIN(g.start.lat, g.end.lat) )
847
0
        return LW_TRUE;
848
0
      else
849
0
        return LW_FALSE;
850
0
    }
851
0
    else
852
      /* Over south pole, test based on north pole */
853
0
    {
854
0
      LWDEBUG(4, "over the south pole...");
855
0
      if ( q.lat < FP_MAX(g.start.lat, g.end.lat) )
856
0
        return LW_TRUE;
857
0
      else
858
0
        return LW_FALSE;
859
0
    }
860
0
  }
861
862
  /* Dateline crossing, flip everything to the opposite hemisphere */
863
0
  else if ( slon > M_PI && ( SIGNUM(g.start.lon) != SIGNUM(g.end.lon) ) )
864
0
  {
865
0
    LWDEBUG(4, "crosses dateline, flip longitudes...");
866
0
    if ( g.start.lon > 0.0 )
867
0
      g.start.lon -= M_PI;
868
0
    else
869
0
      g.start.lon += M_PI;
870
0
    if ( g.end.lon > 0.0 )
871
0
      g.end.lon -= M_PI;
872
0
    else
873
0
      g.end.lon += M_PI;
874
875
0
    if ( q.lon > 0.0 )
876
0
      q.lon -= M_PI;
877
0
    else
878
0
      q.lon += M_PI;
879
0
  }
880
881
0
  if ( ( g.start.lon <= q.lon && q.lon <= g.end.lon ) ||
882
0
       ( g.end.lon <= q.lon && q.lon <= g.start.lon ) )
883
0
  {
884
0
    LWDEBUG(4, "true, this edge contains point");
885
0
    return LW_TRUE;
886
0
  }
887
888
0
  LWDEBUG(4, "false, this edge does not contain point");
889
0
  return LW_FALSE;
890
0
}
891
892
893
/**
894
* Given two points on a unit sphere, calculate their distance apart in radians.
895
*/
896
double sphere_distance(const GEOGRAPHIC_POINT *s, const GEOGRAPHIC_POINT *e)
897
0
{
898
0
  double d_lon, cos_d_lon, cos_lat_e, sin_lat_e, cos_lat_s, sin_lat_s;
899
0
  double a1, a2, a, b;
900
901
0
  if (FP_EQUALS(s->lat, e->lat) && FP_EQUALS(s->lon, e->lon)) return 0.0;
902
0
  d_lon = e->lon - s->lon;
903
0
  cos_d_lon = cos(d_lon);
904
0
  cos_lat_e = cos(e->lat);
905
0
  sin_lat_e = sin(e->lat);
906
0
  cos_lat_s = cos(s->lat);
907
0
  sin_lat_s = sin(s->lat);
908
909
0
  a1 = POW2(cos_lat_e * sin(d_lon));
910
0
  a2 = POW2(cos_lat_s * sin_lat_e - sin_lat_s * cos_lat_e * cos_d_lon);
911
0
  a = sqrt(a1 + a2);
912
0
  b = sin_lat_s * sin_lat_e + cos_lat_s * cos_lat_e * cos_d_lon;
913
0
  return atan2(a, b);
914
0
}
915
916
/**
917
* Given two unit vectors, calculate their distance apart in radians.
918
*/
919
double sphere_distance_cartesian(const POINT3D *s, const POINT3D *e)
920
0
{
921
0
  return acos(FP_MIN(1.0, dot_product(s, e)));
922
0
}
923
924
/**
925
* Given two points on a unit sphere, calculate the direction from s to e.
926
*/
927
double sphere_direction(const GEOGRAPHIC_POINT *s, const GEOGRAPHIC_POINT *e, double d)
928
0
{
929
0
  double heading = 0.0;
930
0
  double f;
931
932
  /* Starting from the poles? Special case. */
933
0
  if ( FP_IS_ZERO(cos(s->lat)) )
934
0
    return (s->lat > 0.0) ? M_PI : 0.0;
935
936
0
  f = (sin(e->lat) - sin(s->lat) * cos(d)) / (sin(d) * cos(s->lat));
937
0
  if ( FP_EQUALS(f, 1.0) )
938
0
    heading = 0.0;
939
0
  else if ( FP_EQUALS(f, -1.0) )
940
0
    heading = M_PI;
941
0
  else if ( fabs(f) > 1.0 )
942
0
  {
943
0
    LWDEBUGF(4, "f = %g", f);
944
0
    heading = acos(f);
945
0
  }
946
0
  else
947
0
    heading = acos(f);
948
949
0
  if ( sin(e->lon - s->lon) < 0.0 )
950
0
    heading = -1 * heading;
951
952
0
  return heading;
953
0
}
954
955
#if 0 /* unused */
956
/**
957
* Computes the spherical excess of a spherical triangle defined by
958
* the three vertices A, B, C. Computes on the unit sphere (i.e., divides
959
* edge lengths by the radius, even if the radius is 1.0). The excess is
960
* signed based on the sign of the delta longitude of A and B.
961
*
962
* @param a The first triangle vertex.
963
* @param b The second triangle vertex.
964
* @param c The last triangle vertex.
965
* @return the signed spherical excess.
966
*/
967
static double sphere_excess(const GEOGRAPHIC_POINT *a, const GEOGRAPHIC_POINT *b, const GEOGRAPHIC_POINT *c)
968
{
969
  double a_dist = sphere_distance(b, c);
970
  double b_dist = sphere_distance(c, a);
971
  double c_dist = sphere_distance(a, b);
972
  double hca = sphere_direction(c, a, b_dist);
973
  double hcb = sphere_direction(c, b, a_dist);
974
  double sign = SIGNUM(hcb-hca);
975
  double ss = (a_dist + b_dist + c_dist) / 2.0;
976
  double E = tan(ss/2.0)*tan((ss-a_dist)/2.0)*tan((ss-b_dist)/2.0)*tan((ss-c_dist)/2.0);
977
  return 4.0 * atan(sqrt(fabs(E))) * sign;
978
}
979
#endif
980
981
982
/**
983
* Returns true if the point p is on the minor edge defined by the
984
* end points of e.
985
*/
986
int edge_contains_point(const GEOGRAPHIC_EDGE *e, const GEOGRAPHIC_POINT *p)
987
0
{
988
0
  if ( edge_point_in_cone(e, p) && edge_point_on_plane(e, p) )
989
    /*  if ( edge_contains_coplanar_point(e, p) && edge_point_on_plane(e, p) ) */
990
0
  {
991
0
    LWDEBUG(4, "point is on edge");
992
0
    return LW_TRUE;
993
0
  }
994
0
  LWDEBUG(4, "point is not on edge");
995
0
  return LW_FALSE;
996
0
}
997
998
/**
999
* Used in great circle to compute the pole of the great circle.
1000
*/
1001
double z_to_latitude(double z, int top)
1002
0
{
1003
0
  double sign = SIGNUM(z);
1004
0
  double tlat = acos(z);
1005
0
  LWDEBUGF(4, "inputs: z(%.8g) sign(%.8g) tlat(%.8g)", z, sign, tlat);
1006
0
  if (FP_IS_ZERO(z))
1007
0
  {
1008
0
    if (top) return M_PI_2;
1009
0
    else return -1.0 * M_PI_2;
1010
0
  }
1011
0
  if (fabs(tlat) > M_PI_2 )
1012
0
  {
1013
0
    tlat = sign * (M_PI - fabs(tlat));
1014
0
  }
1015
0
  else
1016
0
  {
1017
0
    tlat = sign * tlat;
1018
0
  }
1019
0
  LWDEBUGF(4, "output: tlat(%.8g)", tlat);
1020
0
  return tlat;
1021
0
}
1022
1023
/**
1024
* Computes the pole of the great circle disk which is the intersection of
1025
* the great circle with the line of maximum/minimum gradient that lies on
1026
* the great circle plane.
1027
*/
1028
int clairaut_cartesian(const POINT3D *start, const POINT3D *end, GEOGRAPHIC_POINT *g_top, GEOGRAPHIC_POINT *g_bottom)
1029
0
{
1030
0
  POINT3D t1, t2;
1031
0
  GEOGRAPHIC_POINT vN1, vN2;
1032
0
  LWDEBUG(4,"entering function");
1033
0
  unit_normal(start, end, &t1);
1034
0
  unit_normal(end, start, &t2);
1035
0
  LWDEBUGF(4, "unit normal t1 == POINT(%.8g %.8g %.8g)", t1.x, t1.y, t1.z);
1036
0
  LWDEBUGF(4, "unit normal t2 == POINT(%.8g %.8g %.8g)", t2.x, t2.y, t2.z);
1037
0
  cart2geog(&t1, &vN1);
1038
0
  cart2geog(&t2, &vN2);
1039
0
  g_top->lat = z_to_latitude(t1.z,LW_TRUE);
1040
0
  g_top->lon = vN2.lon;
1041
0
  g_bottom->lat = z_to_latitude(t2.z,LW_FALSE);
1042
0
  g_bottom->lon = vN1.lon;
1043
0
  LWDEBUGF(4, "clairaut top == GPOINT(%.6g %.6g)", g_top->lat, g_top->lon);
1044
0
  LWDEBUGF(4, "clairaut bottom == GPOINT(%.6g %.6g)", g_bottom->lat, g_bottom->lon);
1045
0
  return LW_SUCCESS;
1046
0
}
1047
1048
/**
1049
* Computes the pole of the great circle disk which is the intersection of
1050
* the great circle with the line of maximum/minimum gradient that lies on
1051
* the great circle plane.
1052
*/
1053
int clairaut_geographic(const GEOGRAPHIC_POINT *start, const GEOGRAPHIC_POINT *end, GEOGRAPHIC_POINT *g_top, GEOGRAPHIC_POINT *g_bottom)
1054
0
{
1055
0
  POINT3D t1, t2;
1056
0
  GEOGRAPHIC_POINT vN1, vN2;
1057
0
  LWDEBUG(4,"entering function");
1058
0
  robust_cross_product(start, end, &t1);
1059
0
  normalize(&t1);
1060
0
  robust_cross_product(end, start, &t2);
1061
0
  normalize(&t2);
1062
0
  LWDEBUGF(4, "unit normal t1 == POINT(%.8g %.8g %.8g)", t1.x, t1.y, t1.z);
1063
0
  LWDEBUGF(4, "unit normal t2 == POINT(%.8g %.8g %.8g)", t2.x, t2.y, t2.z);
1064
0
  cart2geog(&t1, &vN1);
1065
0
  cart2geog(&t2, &vN2);
1066
0
  g_top->lat = z_to_latitude(t1.z,LW_TRUE);
1067
0
  g_top->lon = vN2.lon;
1068
0
  g_bottom->lat = z_to_latitude(t2.z,LW_FALSE);
1069
0
  g_bottom->lon = vN1.lon;
1070
0
  LWDEBUGF(4, "clairaut top == GPOINT(%.6g %.6g)", g_top->lat, g_top->lon);
1071
0
  LWDEBUGF(4, "clairaut bottom == GPOINT(%.6g %.6g)", g_bottom->lat, g_bottom->lon);
1072
0
  return LW_SUCCESS;
1073
0
}
1074
1075
/**
1076
* Returns true if an intersection can be calculated, and places it in *g.
1077
* Returns false otherwise.
1078
*/
1079
int edge_intersection(const GEOGRAPHIC_EDGE *e1, const GEOGRAPHIC_EDGE *e2, GEOGRAPHIC_POINT *g)
1080
0
{
1081
0
  POINT3D ea, eb, v;
1082
0
  LWDEBUGF(4, "e1 start(%.20g %.20g) end(%.20g %.20g)", e1->start.lat, e1->start.lon, e1->end.lat, e1->end.lon);
1083
0
  LWDEBUGF(4, "e2 start(%.20g %.20g) end(%.20g %.20g)", e2->start.lat, e2->start.lon, e2->end.lat, e2->end.lon);
1084
1085
0
  LWDEBUGF(4, "e1 start(%.20g %.20g) end(%.20g %.20g)", rad2deg(e1->start.lon), rad2deg(e1->start.lat), rad2deg(e1->end.lon), rad2deg(e1->end.lat));
1086
0
  LWDEBUGF(4, "e2 start(%.20g %.20g) end(%.20g %.20g)", rad2deg(e2->start.lon), rad2deg(e2->start.lat), rad2deg(e2->end.lon), rad2deg(e2->end.lat));
1087
1088
0
  if ( geographic_point_equals(&(e1->start), &(e2->start)) )
1089
0
  {
1090
0
    *g = e1->start;
1091
0
    return LW_TRUE;
1092
0
  }
1093
0
  if ( geographic_point_equals(&(e1->end), &(e2->end)) )
1094
0
  {
1095
0
    *g = e1->end;
1096
0
    return LW_TRUE;
1097
0
  }
1098
0
  if ( geographic_point_equals(&(e1->end), &(e2->start)) )
1099
0
  {
1100
0
    *g = e1->end;
1101
0
    return LW_TRUE;
1102
0
  }
1103
0
  if ( geographic_point_equals(&(e1->start), &(e2->end)) )
1104
0
  {
1105
0
    *g = e1->start;
1106
0
    return LW_TRUE;
1107
0
  }
1108
1109
0
  robust_cross_product(&(e1->start), &(e1->end), &ea);
1110
0
  normalize(&ea);
1111
0
  robust_cross_product(&(e2->start), &(e2->end), &eb);
1112
0
  normalize(&eb);
1113
0
  LWDEBUGF(4, "e1 cross product == POINT(%.12g %.12g %.12g)", ea.x, ea.y, ea.z);
1114
0
  LWDEBUGF(4, "e2 cross product == POINT(%.12g %.12g %.12g)", eb.x, eb.y, eb.z);
1115
0
  LWDEBUGF(4, "fabs(dot_product(ea, eb)) == %.14g", fabs(dot_product(&ea, &eb)));
1116
0
  if ( FP_EQUALS(fabs(dot_product(&ea, &eb)), 1.0) )
1117
0
  {
1118
0
    LWDEBUGF(4, "parallel edges found! dot_product = %.12g", dot_product(&ea, &eb));
1119
    /* Parallel (maybe equal) edges! */
1120
    /* Hack alert, only returning ONE end of the edge right now, most do better later. */
1121
    /* Hack alert #2, returning a value of 2 to indicate a co-linear crossing event. */
1122
0
    if ( edge_contains_point(e1, &(e2->start)) )
1123
0
    {
1124
0
      *g = e2->start;
1125
0
      return 2;
1126
0
    }
1127
0
    if ( edge_contains_point(e1, &(e2->end)) )
1128
0
    {
1129
0
      *g = e2->end;
1130
0
      return 2;
1131
0
    }
1132
0
    if ( edge_contains_point(e2, &(e1->start)) )
1133
0
    {
1134
0
      *g = e1->start;
1135
0
      return 2;
1136
0
    }
1137
0
    if ( edge_contains_point(e2, &(e1->end)) )
1138
0
    {
1139
0
      *g = e1->end;
1140
0
      return 2;
1141
0
    }
1142
0
  }
1143
0
  unit_normal(&ea, &eb, &v);
1144
0
  LWDEBUGF(4, "v == POINT(%.12g %.12g %.12g)", v.x, v.y, v.z);
1145
0
  g->lat = atan2(v.z, sqrt(v.x * v.x + v.y * v.y));
1146
0
  g->lon = atan2(v.y, v.x);
1147
0
  LWDEBUGF(4, "g == GPOINT(%.12g %.12g)", g->lat, g->lon);
1148
0
  LWDEBUGF(4, "g == POINT(%.12g %.12g)", rad2deg(g->lon), rad2deg(g->lat));
1149
0
  if ( edge_contains_point(e1, g) && edge_contains_point(e2, g) )
1150
0
  {
1151
0
    return LW_TRUE;
1152
0
  }
1153
0
  else
1154
0
  {
1155
0
    LWDEBUG(4, "flipping point to other side of sphere");
1156
0
    g->lat = -1.0 * g->lat;
1157
0
    g->lon = g->lon + M_PI;
1158
0
    if ( g->lon > M_PI )
1159
0
    {
1160
0
      g->lon = -1.0 * (2.0 * M_PI - g->lon);
1161
0
    }
1162
0
    if ( edge_contains_point(e1, g) && edge_contains_point(e2, g) )
1163
0
    {
1164
0
      return LW_TRUE;
1165
0
    }
1166
0
  }
1167
0
  return LW_FALSE;
1168
0
}
1169
1170
double edge_distance_to_point(const GEOGRAPHIC_EDGE *e, const GEOGRAPHIC_POINT *gp, GEOGRAPHIC_POINT *closest)
1171
0
{
1172
0
  double d1 = 1000000000.0, d2, d3, d_nearest;
1173
0
  POINT3D n, p, k;
1174
0
  GEOGRAPHIC_POINT gk, g_nearest;
1175
1176
  /* Zero length edge, */
1177
0
  if ( geographic_point_equals(&(e->start), &(e->end)) )
1178
0
  {
1179
0
    if (closest)
1180
0
      *closest = e->start;
1181
1182
0
    return sphere_distance(&(e->start), gp);
1183
0
  }
1184
1185
0
  robust_cross_product(&(e->start), &(e->end), &n);
1186
0
  normalize(&n);
1187
0
  geog2cart(gp, &p);
1188
0
  vector_scale(&n, dot_product(&p, &n));
1189
0
  vector_difference(&p, &n, &k);
1190
0
  normalize(&k);
1191
0
  cart2geog(&k, &gk);
1192
0
  if ( edge_point_in_cone(e, &gk) )
1193
0
  {
1194
0
    d1 = sphere_distance(gp, &gk);
1195
0
  }
1196
0
  d2 = sphere_distance(gp, &(e->start));
1197
0
  d3 = sphere_distance(gp, &(e->end));
1198
1199
0
  d_nearest = d1;
1200
0
  g_nearest = gk;
1201
1202
0
  if ( d2 < d_nearest )
1203
0
  {
1204
0
    d_nearest = d2;
1205
0
    g_nearest = e->start;
1206
0
  }
1207
0
  if ( d3 < d_nearest )
1208
0
  {
1209
0
    d_nearest = d3;
1210
0
    g_nearest = e->end;
1211
0
  }
1212
0
  if (closest)
1213
0
    *closest = g_nearest;
1214
1215
0
  return d_nearest;
1216
0
}
1217
1218
/**
1219
* Calculate the distance between two edges.
1220
* IMPORTANT: this test does not check for edge intersection!!! (distance == 0)
1221
* You have to check for intersection before calling this function.
1222
*/
1223
double edge_distance_to_edge(const GEOGRAPHIC_EDGE *e1, const GEOGRAPHIC_EDGE *e2, GEOGRAPHIC_POINT *closest1, GEOGRAPHIC_POINT *closest2)
1224
0
{
1225
0
  double d;
1226
0
  GEOGRAPHIC_POINT gcp1s, gcp1e, gcp2s, gcp2e, c1, c2;
1227
0
  double d1s = edge_distance_to_point(e1, &(e2->start), &gcp1s);
1228
0
  double d1e = edge_distance_to_point(e1, &(e2->end), &gcp1e);
1229
0
  double d2s = edge_distance_to_point(e2, &(e1->start), &gcp2s);
1230
0
  double d2e = edge_distance_to_point(e2, &(e1->end), &gcp2e);
1231
1232
0
  d = d1s;
1233
0
  c1 = gcp1s;
1234
0
  c2 = e2->start;
1235
1236
0
  if ( d1e < d )
1237
0
  {
1238
0
    d = d1e;
1239
0
    c1 = gcp1e;
1240
0
    c2 = e2->end;
1241
0
  }
1242
1243
0
  if ( d2s < d )
1244
0
  {
1245
0
    d = d2s;
1246
0
    c1 = e1->start;
1247
0
    c2 = gcp2s;
1248
0
  }
1249
1250
0
  if ( d2e < d )
1251
0
  {
1252
0
    d = d2e;
1253
0
    c1 = e1->end;
1254
0
    c2 = gcp2e;
1255
0
  }
1256
1257
0
  if ( closest1 ) *closest1 = c1;
1258
0
  if ( closest2 ) *closest2 = c2;
1259
1260
0
  return d;
1261
0
}
1262
1263
1264
/**
1265
* Given a starting location r, a distance and an azimuth
1266
* to the new point, compute the location of the projected point on the unit sphere.
1267
*/
1268
int sphere_project(const GEOGRAPHIC_POINT *r, double distance, double azimuth, GEOGRAPHIC_POINT *n)
1269
0
{
1270
0
  double d = distance;
1271
0
  double lat1 = r->lat;
1272
0
  double lon1 = r->lon;
1273
0
  double lat2, lon2;
1274
1275
0
  lat2 = asin(sin(lat1)*cos(d) + cos(lat1)*sin(d)*cos(azimuth));
1276
1277
  /* If we're going straight up or straight down, we don't need to calculate the longitude */
1278
  /* TODO: this isn't quite true, what if we're going over the pole? */
1279
0
  if ( FP_EQUALS(azimuth, M_PI) || FP_EQUALS(azimuth, 0.0) )
1280
0
  {
1281
0
    lon2 = r->lon;
1282
0
  }
1283
0
  else
1284
0
  {
1285
0
    lon2 = lon1 + atan2(sin(azimuth)*sin(d)*cos(lat1), cos(d)-sin(lat1)*sin(lat2));
1286
0
  }
1287
1288
0
  if ( isnan(lat2) || isnan(lon2) )
1289
0
    return LW_FAILURE;
1290
1291
0
  n->lat = lat2;
1292
0
  n->lon = lon2;
1293
1294
0
  return LW_SUCCESS;
1295
0
}
1296
1297
1298
int edge_calculate_gbox_slow(const GEOGRAPHIC_EDGE *e, GBOX *gbox)
1299
0
{
1300
0
  int steps = 1000000;
1301
0
  int i;
1302
0
  double dx, dy, dz;
1303
0
  double distance = sphere_distance(&(e->start), &(e->end));
1304
0
  POINT3D pn, p, start, end;
1305
1306
  /* Edge is zero length, just return the naive box */
1307
0
  if ( FP_IS_ZERO(distance) )
1308
0
  {
1309
0
    LWDEBUG(4, "edge is zero length. returning");
1310
0
    geog2cart(&(e->start), &start);
1311
0
    geog2cart(&(e->end), &end);
1312
0
    gbox_init_point3d(&start, gbox);
1313
0
    gbox_merge_point3d(&end, gbox);
1314
0
    return LW_SUCCESS;
1315
0
  }
1316
1317
  /* Edge is antipodal (one point on each side of the globe),
1318
     set the box to contain the whole world and return */
1319
0
  if ( FP_EQUALS(distance, M_PI) )
1320
0
  {
1321
0
    LWDEBUG(4, "edge is antipodal. setting to maximum size box, and returning");
1322
0
    gbox->xmin = gbox->ymin = gbox->zmin = -1.0;
1323
0
    gbox->xmax = gbox->ymax = gbox->zmax = 1.0;
1324
0
    return LW_SUCCESS;
1325
0
  }
1326
1327
  /* Walk along the chord between start and end incrementally,
1328
     normalizing at each step. */
1329
0
  geog2cart(&(e->start), &start);
1330
0
  geog2cart(&(e->end), &end);
1331
0
  dx = (end.x - start.x)/steps;
1332
0
  dy = (end.y - start.y)/steps;
1333
0
  dz = (end.z - start.z)/steps;
1334
0
  p = start;
1335
0
  gbox->xmin = gbox->xmax = p.x;
1336
0
  gbox->ymin = gbox->ymax = p.y;
1337
0
  gbox->zmin = gbox->zmax = p.z;
1338
0
  for ( i = 0; i < steps; i++ )
1339
0
  {
1340
0
    p.x += dx;
1341
0
    p.y += dy;
1342
0
    p.z += dz;
1343
0
    pn = p;
1344
0
    normalize(&pn);
1345
0
    gbox_merge_point3d(&pn, gbox);
1346
0
  }
1347
0
  return LW_SUCCESS;
1348
0
}
1349
1350
/**
1351
* The magic function, given an edge in spherical coordinates, calculate a
1352
* 3D bounding box that fully contains it, taking into account the curvature
1353
* of the sphere on which it is inscribed.
1354
*
1355
* Any arc on the sphere defines a plane that bisects the sphere. In this plane,
1356
* the arc is a portion of a unit circle.
1357
* Projecting the end points of the axes (1,0,0), (-1,0,0) etc, into the plane
1358
* and normalizing yields potential extrema points. Those points on the
1359
* side of the plane-dividing line formed by the end points that is opposite
1360
* the origin of the plane are extrema and should be added to the bounding box.
1361
*/
1362
int edge_calculate_gbox(const POINT3D *A1, const POINT3D *A2, GBOX *gbox)
1363
0
{
1364
0
  POINT2D R1, R2, RX, O;
1365
0
  POINT3D AN, A3;
1366
0
  POINT3D X[6];
1367
0
  int i, o_side;
1368
1369
  /* Initialize the box with the edge end points */
1370
0
  gbox_init_point3d(A1, gbox);
1371
0
  gbox_merge_point3d(A2, gbox);
1372
1373
  /* Zero length edge, just return! */
1374
0
  if ( p3d_same(A1, A2) )
1375
0
    return LW_SUCCESS;
1376
1377
  /* Error out on antipodal edge */
1378
0
  if ( FP_EQUALS(A1->x, -1*A2->x) && FP_EQUALS(A1->y, -1*A2->y) && FP_EQUALS(A1->z, -1*A2->z) )
1379
0
  {
1380
0
    lwerror("Antipodal (180 degrees long) edge detected!");
1381
0
    return LW_FAILURE;
1382
0
  }
1383
1384
  /* Create A3, a vector in the plane of A1/A2, orthogonal to A1  */
1385
0
  unit_normal(A1, A2, &AN);
1386
0
  unit_normal(&AN, A1, &A3);
1387
1388
  /* Project A1 and A2 into the 2-space formed by the plane A1/A3 */
1389
0
  R1.x = 1.0;
1390
0
  R1.y = 0.0;
1391
0
  R2.x = dot_product(A2, A1);
1392
0
  R2.y = dot_product(A2, &A3);
1393
1394
  /* Initialize our 3-space axis points (x+, x-, y+, y-, z+, z-) */
1395
0
  memset(X, 0, sizeof(POINT3D) * 6);
1396
0
  X[0].x = X[2].y = X[4].z =  1.0;
1397
0
  X[1].x = X[3].y = X[5].z = -1.0;
1398
1399
  /* Initialize a 2-space origin point. */
1400
0
  O.x = O.y = 0.0;
1401
  /* What side of the line joining R1/R2 is O? */
1402
0
  o_side = lw_segment_side(&R1, &R2, &O);
1403
1404
  /* Add any extrema! */
1405
0
  for ( i = 0; i < 6; i++ )
1406
0
  {
1407
    /* Convert 3-space axis points to 2-space unit vectors */
1408
0
    RX.x = dot_product(&(X[i]), A1);
1409
0
    RX.y = dot_product(&(X[i]), &A3);
1410
0
    normalize2d(&RX);
1411
1412
    /* Any axis end on the side of R1/R2 opposite the origin */
1413
    /* is an extreme point in the arc, so we add the 3-space */
1414
    /* version of the point on R1/R2 to the gbox */
1415
0
    if ( lw_segment_side(&R1, &R2, &RX) != o_side )
1416
0
    {
1417
0
      POINT3D Xn;
1418
0
      Xn.x = RX.x * A1->x + RX.y * A3.x;
1419
0
      Xn.y = RX.x * A1->y + RX.y * A3.y;
1420
0
      Xn.z = RX.x * A1->z + RX.y * A3.z;
1421
1422
0
      gbox_merge_point3d(&Xn, gbox);
1423
0
    }
1424
0
  }
1425
1426
0
  return LW_SUCCESS;
1427
0
}
1428
1429
/*
1430
* When we have a globe-covering gbox but we still want an outside
1431
* point, we do this Very Bad Hack, which is look at the first two points
1432
* in the ring and then nudge a point to the left of that arc.
1433
* There is an assumption of convexity built in there, as well as that
1434
* the shape doesn't have a sharp reversal in it. It's ugly, but
1435
* it fixes some common cases (large selection polygons) that users
1436
* are generating. At some point all of geodetic needs a clean-room
1437
* rewrite.
1438
* There is also an assumption of CCW exterior ring, which is how the
1439
* GeoJSON spec defined geographic ring orientation.
1440
*/
1441
static int lwpoly_pt_outside_hack(const LWPOLY *poly, POINT2D *pt_outside)
1442
0
{
1443
0
  GEOGRAPHIC_POINT g1, g2, gSum;
1444
0
  POINT4D p1, p2;
1445
0
  POINT3D q1, q2, qMid, qCross, qSum;
1446
0
  POINTARRAY *pa;
1447
0
  if (lwgeom_is_empty((LWGEOM*)poly))
1448
0
    return LW_FAILURE;
1449
0
  if (poly->nrings < 1)
1450
0
    return LW_FAILURE;
1451
0
  pa = poly->rings[0];
1452
0
  if (pa->npoints < 2)
1453
0
    return LW_FAILURE;
1454
1455
  /* First two points of ring */
1456
0
  getPoint4d_p(pa, 0, &p1);
1457
0
  getPoint4d_p(pa, 1, &p2);
1458
  /* Convert to XYZ unit vectors */
1459
0
  geographic_point_init(p1.x, p1.y, &g1);
1460
0
  geographic_point_init(p2.x, p2.y, &g2);
1461
0
  geog2cart(&g1, &q1);
1462
0
  geog2cart(&g2, &q2);
1463
  /* Mid-point of first two points */
1464
0
  vector_sum(&q1, &q2, &qMid);
1465
0
  normalize(&qMid);
1466
  /* Cross product of first two points (perpendicular) */
1467
0
  cross_product(&q1, &q2, &qCross);
1468
0
  normalize(&qCross);
1469
  /* Invert it to put it outside, and scale down */
1470
0
  vector_scale(&qCross, -0.2);
1471
  /* Project midpoint to the right */
1472
0
  vector_sum(&qMid, &qCross, &qSum);
1473
0
  normalize(&qSum);
1474
  /* Convert back to lon/lat */
1475
0
  cart2geog(&qSum, &gSum);
1476
0
  pt_outside->x = rad2deg(gSum.lon);
1477
0
  pt_outside->y = rad2deg(gSum.lat);
1478
0
  return LW_SUCCESS;
1479
0
}
1480
1481
int lwpoly_pt_outside(const LWPOLY *poly, POINT2D *pt_outside)
1482
0
{
1483
0
  int rv;
1484
  /* Make sure we have boxes */
1485
0
  if ( poly->bbox )
1486
0
  {
1487
0
    rv = gbox_pt_outside(poly->bbox, pt_outside);
1488
0
  }
1489
0
  else
1490
0
  {
1491
0
    GBOX gbox;
1492
0
    lwgeom_calculate_gbox_geodetic((LWGEOM*)poly, &gbox);
1493
0
    rv = gbox_pt_outside(&gbox, pt_outside);
1494
0
  }
1495
1496
0
  if (rv == LW_FALSE)
1497
0
    return lwpoly_pt_outside_hack(poly, pt_outside);
1498
1499
0
  return rv;
1500
0
}
1501
1502
/**
1503
* Given a unit geocentric gbox, return a lon/lat (degrees) coordinate point point that is
1504
* guaranteed to be outside the box (and therefore anything it contains).
1505
*/
1506
int gbox_pt_outside(const GBOX *gbox, POINT2D *pt_outside)
1507
0
{
1508
0
  double grow = M_PI / 180.0 / 60.0; /* one arc-minute */
1509
0
  int i;
1510
0
  GBOX ge;
1511
0
  POINT3D corners[8];
1512
0
  POINT3D pt;
1513
0
  GEOGRAPHIC_POINT g;
1514
1515
0
  while ( grow < M_PI )
1516
0
  {
1517
    /* Assign our box and expand it slightly. */
1518
0
    ge = *gbox;
1519
0
    if ( ge.xmin > -1 ) ge.xmin -= grow;
1520
0
    if ( ge.ymin > -1 ) ge.ymin -= grow;
1521
0
    if ( ge.zmin > -1 ) ge.zmin -= grow;
1522
0
    if ( ge.xmax < 1 )  ge.xmax += grow;
1523
0
    if ( ge.ymax < 1 )  ge.ymax += grow;
1524
0
    if ( ge.zmax < 1 )  ge.zmax += grow;
1525
1526
    /* Build our eight corner points */
1527
0
    corners[0].x = ge.xmin;
1528
0
    corners[0].y = ge.ymin;
1529
0
    corners[0].z = ge.zmin;
1530
1531
0
    corners[1].x = ge.xmin;
1532
0
    corners[1].y = ge.ymax;
1533
0
    corners[1].z = ge.zmin;
1534
1535
0
    corners[2].x = ge.xmin;
1536
0
    corners[2].y = ge.ymin;
1537
0
    corners[2].z = ge.zmax;
1538
1539
0
    corners[3].x = ge.xmax;
1540
0
    corners[3].y = ge.ymin;
1541
0
    corners[3].z = ge.zmin;
1542
1543
0
    corners[4].x = ge.xmax;
1544
0
    corners[4].y = ge.ymax;
1545
0
    corners[4].z = ge.zmin;
1546
1547
0
    corners[5].x = ge.xmax;
1548
0
    corners[5].y = ge.ymin;
1549
0
    corners[5].z = ge.zmax;
1550
1551
0
    corners[6].x = ge.xmin;
1552
0
    corners[6].y = ge.ymax;
1553
0
    corners[6].z = ge.zmax;
1554
1555
0
    corners[7].x = ge.xmax;
1556
0
    corners[7].y = ge.ymax;
1557
0
    corners[7].z = ge.zmax;
1558
1559
0
    LWDEBUG(4, "trying to use a box corner point...");
1560
0
    for ( i = 0; i < 8; i++ )
1561
0
    {
1562
0
      normalize(&(corners[i]));
1563
0
      LWDEBUGF(4, "testing corner %d: POINT(%.8g %.8g %.8g)", i, corners[i].x, corners[i].y, corners[i].z);
1564
0
      if ( ! gbox_contains_point3d(gbox, &(corners[i])) )
1565
0
      {
1566
0
        LWDEBUGF(4, "corner %d is outside our gbox", i);
1567
0
        pt = corners[i];
1568
0
        normalize(&pt);
1569
0
        cart2geog(&pt, &g);
1570
0
        pt_outside->x = rad2deg(g.lon);
1571
0
        pt_outside->y = rad2deg(g.lat);
1572
0
        LWDEBUGF(4, "returning POINT(%.8g %.8g) as outside point", pt_outside->x, pt_outside->y);
1573
0
        return LW_SUCCESS;
1574
0
      }
1575
0
    }
1576
1577
    /* Try a wider growth to push the corners outside the original box. */
1578
0
    grow *= 2.0;
1579
0
  }
1580
1581
  /* This should never happen! */
1582
  // lwerror("BOOM! Could not generate outside point!");
1583
0
  return LW_FAILURE;
1584
0
}
1585
1586
1587
static int ptarray_segmentize_sphere_edge_recursive (
1588
  const POINT3D *p1, const POINT3D *p2, /* 3-space points we are interpolating between */
1589
  const POINT4D *v1, const POINT4D *v2, /* real values and z/m values */
1590
  double d, double max_seg_length, /* current segment length and segment limit */
1591
  POINTARRAY *pa) /* write out results here */
1592
0
{
1593
0
  GEOGRAPHIC_POINT g;
1594
  /* Reached the terminal leaf in recursion. Add */
1595
  /* the left-most point to the pointarray here */
1596
  /* We recurse down the left side first, so outputs should */
1597
  /* end up added to the array in order this way */
1598
0
  if (d <= max_seg_length)
1599
0
  {
1600
0
    POINT4D p;
1601
0
    cart2geog(p1, &g);
1602
0
    p.x = v1->x;
1603
0
    p.y = v1->y;
1604
0
    p.z = v1->z;
1605
0
    p.m = v1->m;
1606
0
    return ptarray_append_point(pa, &p, LW_FALSE);
1607
0
  }
1608
  /* Find the mid-point and recurse on the left and then the right */
1609
0
  else
1610
0
  {
1611
    /* Calculate mid-point */
1612
0
    POINT3D mid;
1613
0
    mid.x = (p1->x + p2->x) / 2.0;
1614
0
    mid.y = (p1->y + p2->y) / 2.0;
1615
0
    mid.z = (p1->z + p2->z) / 2.0;
1616
0
    normalize(&mid);
1617
1618
    /* Calculate z/m mid-values */
1619
0
    POINT4D midv;
1620
0
    cart2geog(&mid, &g);
1621
0
    midv.x = rad2deg(g.lon);
1622
0
    midv.y = rad2deg(g.lat);
1623
0
    midv.z = (v1->z + v2->z) / 2.0;
1624
0
    midv.m = (v1->m + v2->m) / 2.0;
1625
    /* Recurse on the left first */
1626
0
    ptarray_segmentize_sphere_edge_recursive(p1, &mid, v1, &midv, d/2.0, max_seg_length, pa);
1627
0
    ptarray_segmentize_sphere_edge_recursive(&mid, p2, &midv, v2, d/2.0, max_seg_length, pa);
1628
0
    return LW_SUCCESS;
1629
0
  }
1630
0
}
1631
1632
/**
1633
* Create a new point array with no segment longer than the input segment length (expressed in radians!)
1634
* @param pa_in - input point array pointer
1635
* @param max_seg_length - maximum output segment length in radians
1636
*/
1637
static POINTARRAY*
1638
ptarray_segmentize_sphere(const POINTARRAY *pa_in, double max_seg_length)
1639
0
{
1640
0
  POINTARRAY *pa_out;
1641
0
  int hasz = ptarray_has_z(pa_in);
1642
0
  int hasm = ptarray_has_m(pa_in);
1643
0
  POINT4D p1, p2;
1644
0
  POINT3D q1, q2;
1645
0
  GEOGRAPHIC_POINT g1, g2;
1646
0
  uint32_t i;
1647
1648
  /* Just crap out on crazy input */
1649
0
  if ( ! pa_in )
1650
0
    lwerror("%s: null input pointarray", __func__);
1651
0
  if ( max_seg_length <= 0.0 )
1652
0
    lwerror("%s: maximum segment length must be positive", __func__);
1653
1654
  /* Empty starting array */
1655
0
  pa_out = ptarray_construct_empty(hasz, hasm, pa_in->npoints);
1656
1657
  /* Simple loop per edge */
1658
0
  for (i = 1; i < pa_in->npoints; i++)
1659
0
  {
1660
0
    getPoint4d_p(pa_in, i-1, &p1);
1661
0
    getPoint4d_p(pa_in, i, &p2);
1662
0
    geographic_point_init(p1.x, p1.y, &g1);
1663
0
    geographic_point_init(p2.x, p2.y, &g2);
1664
1665
    /* Skip duplicate points (except in case of 2-point lines!) */
1666
0
    if ((pa_in->npoints > 2) && p4d_same(&p1, &p2))
1667
0
      continue;
1668
1669
    /* How long is this edge? */
1670
0
    double d = sphere_distance(&g1, &g2);
1671
1672
0
    if (d > max_seg_length)
1673
0
    {
1674
0
      geog2cart(&g1, &q1);
1675
0
      geog2cart(&g2, &q2);
1676
      /* 3-d end points, XYZM end point, current edge size, min edge size */
1677
0
      ptarray_segmentize_sphere_edge_recursive(&q1, &q2, &p1, &p2, d, max_seg_length, pa_out);
1678
0
    }
1679
    /* If we don't segmentize, we need to add first point manually */
1680
0
    else
1681
0
    {
1682
0
      ptarray_append_point(pa_out, &p1, LW_TRUE);
1683
0
    }
1684
0
  }
1685
  /* Always add the last point */
1686
0
  ptarray_append_point(pa_out, &p2, LW_TRUE);
1687
0
  return pa_out;
1688
0
}
1689
1690
/**
1691
* Create a new, densified geometry where no segment is longer than max_seg_length.
1692
* Input geometry is not altered, output geometry must be freed by caller.
1693
* @param lwg_in = input geometry
1694
* @param max_seg_length = maximum segment length in radians
1695
*/
1696
LWGEOM*
1697
lwgeom_segmentize_sphere(const LWGEOM *lwg_in, double max_seg_length)
1698
0
{
1699
0
  POINTARRAY *pa_out;
1700
0
  LWLINE *lwline;
1701
0
  LWPOLY *lwpoly_in, *lwpoly_out;
1702
0
  LWCOLLECTION *lwcol_in, *lwcol_out;
1703
0
  uint32_t i;
1704
1705
  /* Reflect NULL */
1706
0
  if ( ! lwg_in )
1707
0
    return NULL;
1708
1709
  /* Clone empty */
1710
0
  if ( lwgeom_is_empty(lwg_in) )
1711
0
    return lwgeom_clone(lwg_in);
1712
1713
0
  switch (lwg_in->type)
1714
0
  {
1715
0
  case MULTIPOINTTYPE:
1716
0
  case POINTTYPE:
1717
0
    return lwgeom_clone_deep(lwg_in);
1718
0
    break;
1719
0
  case LINETYPE:
1720
0
    lwline = lwgeom_as_lwline(lwg_in);
1721
0
    pa_out = ptarray_segmentize_sphere(lwline->points, max_seg_length);
1722
0
    return lwline_as_lwgeom(lwline_construct(lwg_in->srid, NULL, pa_out));
1723
0
    break;
1724
0
  case POLYGONTYPE:
1725
0
    lwpoly_in = lwgeom_as_lwpoly(lwg_in);
1726
0
    lwpoly_out = lwpoly_construct_empty(lwg_in->srid, lwgeom_has_z(lwg_in), lwgeom_has_m(lwg_in));
1727
0
    for ( i = 0; i < lwpoly_in->nrings; i++ )
1728
0
    {
1729
0
      pa_out = ptarray_segmentize_sphere(lwpoly_in->rings[i], max_seg_length);
1730
0
      lwpoly_add_ring(lwpoly_out, pa_out);
1731
0
    }
1732
0
    return lwpoly_as_lwgeom(lwpoly_out);
1733
0
    break;
1734
0
  case MULTILINETYPE:
1735
0
  case MULTIPOLYGONTYPE:
1736
0
  case COLLECTIONTYPE:
1737
0
    lwcol_in = lwgeom_as_lwcollection(lwg_in);
1738
0
    lwcol_out = lwcollection_construct_empty(lwg_in->type, lwg_in->srid, lwgeom_has_z(lwg_in), lwgeom_has_m(lwg_in));
1739
0
    for ( i = 0; i < lwcol_in->ngeoms; i++ )
1740
0
    {
1741
0
      lwcollection_add_lwgeom(lwcol_out, lwgeom_segmentize_sphere(lwcol_in->geoms[i], max_seg_length));
1742
0
    }
1743
0
    return lwcollection_as_lwgeom(lwcol_out);
1744
0
    break;
1745
0
  default:
1746
0
    lwerror("lwgeom_segmentize_sphere: unsupported input geometry type: %d - %s",
1747
0
            lwg_in->type, lwtype_name(lwg_in->type));
1748
0
    break;
1749
0
  }
1750
1751
0
  lwerror("lwgeom_segmentize_sphere got to the end of the function, should not happen");
1752
0
  return NULL;
1753
0
}
1754
1755
1756
static double ptarray_distance_spheroid(const POINTARRAY *pa1, const POINTARRAY *pa2, const SPHEROID *s, double tolerance, int check_intersection)
1757
0
{
1758
0
  GEOGRAPHIC_EDGE e1, e2;
1759
0
  GEOGRAPHIC_POINT g1, g2;
1760
0
  GEOGRAPHIC_POINT nearest1, nearest2;
1761
0
  POINT3D A1, A2, B1, B2;
1762
0
  const POINT2D *p;
1763
0
  double distance;
1764
0
  uint32_t i, j;
1765
0
  int use_sphere = (s->a == s->b ? 1 : 0);
1766
1767
  /* Make result really big, so that everything will be smaller than it */
1768
0
  distance = FLT_MAX;
1769
1770
  /* Empty point arrays? Return negative */
1771
0
  if ( pa1->npoints == 0 || pa2->npoints == 0 )
1772
0
    return -1.0;
1773
1774
  /* Handle point/point case here */
1775
0
  if ( pa1->npoints == 1 && pa2->npoints == 1 )
1776
0
  {
1777
0
    p = getPoint2d_cp(pa1, 0);
1778
0
    geographic_point_init(p->x, p->y, &g1);
1779
0
    p = getPoint2d_cp(pa2, 0);
1780
0
    geographic_point_init(p->x, p->y, &g2);
1781
    /* Sphere special case, axes equal */
1782
0
    distance = s->radius * sphere_distance(&g1, &g2);
1783
0
    if ( use_sphere )
1784
0
      return distance;
1785
    /* Below tolerance, actual distance isn't of interest */
1786
0
    else if ( distance < 0.95 * tolerance )
1787
0
      return distance;
1788
    /* Close or greater than tolerance, get the real answer to be sure */
1789
0
    else
1790
0
      return spheroid_distance(&g1, &g2, s);
1791
0
  }
1792
1793
  /* Handle point/line case here */
1794
0
  if ( pa1->npoints == 1 || pa2->npoints == 1 )
1795
0
  {
1796
    /* Handle one/many case here */
1797
0
    uint32_t i;
1798
0
    const POINTARRAY *pa_one;
1799
0
    const POINTARRAY *pa_many;
1800
1801
0
    if ( pa1->npoints == 1 )
1802
0
    {
1803
0
      pa_one = pa1;
1804
0
      pa_many = pa2;
1805
0
    }
1806
0
    else
1807
0
    {
1808
0
      pa_one = pa2;
1809
0
      pa_many = pa1;
1810
0
    }
1811
1812
    /* Initialize our point */
1813
0
    p = getPoint2d_cp(pa_one, 0);
1814
0
    geographic_point_init(p->x, p->y, &g1);
1815
1816
    /* Initialize start of line */
1817
0
    p = getPoint2d_cp(pa_many, 0);
1818
0
    geographic_point_init(p->x, p->y, &(e1.start));
1819
1820
    /* Iterate through the edges in our line */
1821
0
    for ( i = 1; i < pa_many->npoints; i++ )
1822
0
    {
1823
0
      double d;
1824
0
      p = getPoint2d_cp(pa_many, i);
1825
0
      geographic_point_init(p->x, p->y, &(e1.end));
1826
      /* Get the spherical distance between point and edge */
1827
0
      d = s->radius * edge_distance_to_point(&e1, &g1, &g2);
1828
      /* New shortest distance! Record this distance / location */
1829
0
      if ( d < distance )
1830
0
      {
1831
0
        distance = d;
1832
0
        nearest2 = g2;
1833
0
      }
1834
      /* We've gotten closer than the tolerance... */
1835
0
      if ( d <= tolerance )
1836
0
      {
1837
        /* Working on a sphere? The answer is correct, return */
1838
0
        if ( use_sphere )
1839
0
        {
1840
0
          return d;
1841
0
        }
1842
        /* Far enough past the tolerance that the spheroid calculation won't change things */
1843
0
        else if ( d <= tolerance * 0.95 )
1844
0
        {
1845
0
          return d;
1846
0
        }
1847
        /* On a spheroid and near the tolerance? Confirm that we are *actually* closer than tolerance */
1848
0
        else
1849
0
        {
1850
0
          d = spheroid_distance(&g1, &nearest2, s);
1851
          /* Yes, closer than tolerance, return! */
1852
0
          if ( d <= tolerance )
1853
0
            return d;
1854
0
        }
1855
0
      }
1856
0
      e1.start = e1.end;
1857
0
    }
1858
1859
    /* On sphere, return answer */
1860
0
    if ( use_sphere )
1861
0
      return distance;
1862
    /* On spheroid, calculate final answer based on closest approach */
1863
0
    else
1864
0
      return spheroid_distance(&g1, &nearest2, s);
1865
1866
0
  }
1867
1868
  /* Initialize start of line 1 */
1869
0
  p = getPoint2d_cp(pa1, 0);
1870
0
  geographic_point_init(p->x, p->y, &(e1.start));
1871
0
  geog2cart(&(e1.start), &A1);
1872
1873
1874
  /* Handle line/line case */
1875
0
  for ( i = 1; i < pa1->npoints; i++ )
1876
0
  {
1877
0
    p = getPoint2d_cp(pa1, i);
1878
0
    geographic_point_init(p->x, p->y, &(e1.end));
1879
0
    geog2cart(&(e1.end), &A2);
1880
1881
    /* Initialize start of line 2 */
1882
0
    p = getPoint2d_cp(pa2, 0);
1883
0
    geographic_point_init(p->x, p->y, &(e2.start));
1884
0
    geog2cart(&(e2.start), &B1);
1885
1886
0
    for ( j = 1; j < pa2->npoints; j++ )
1887
0
    {
1888
0
      double d;
1889
1890
0
      p = getPoint2d_cp(pa2, j);
1891
0
      geographic_point_init(p->x, p->y, &(e2.end));
1892
0
      geog2cart(&(e2.end), &B2);
1893
1894
0
      LWDEBUGF(4, "e1.start == GPOINT(%.6g %.6g) ", e1.start.lat, e1.start.lon);
1895
0
      LWDEBUGF(4, "e1.end == GPOINT(%.6g %.6g) ", e1.end.lat, e1.end.lon);
1896
0
      LWDEBUGF(4, "e2.start == GPOINT(%.6g %.6g) ", e2.start.lat, e2.start.lon);
1897
0
      LWDEBUGF(4, "e2.end == GPOINT(%.6g %.6g) ", e2.end.lat, e2.end.lon);
1898
1899
0
      if ( check_intersection && edge_intersects(&A1, &A2, &B1, &B2) )
1900
0
      {
1901
0
        LWDEBUG(4,"edge intersection! returning 0.0");
1902
0
        return 0.0;
1903
0
      }
1904
0
      d = s->radius * edge_distance_to_edge(&e1, &e2, &g1, &g2);
1905
0
      LWDEBUGF(4,"got edge_distance_to_edge %.8g", d);
1906
1907
0
      if ( d < distance )
1908
0
      {
1909
0
        distance = d;
1910
0
        nearest1 = g1;
1911
0
        nearest2 = g2;
1912
0
      }
1913
0
      if ( d <= tolerance )
1914
0
      {
1915
0
        if ( use_sphere )
1916
0
        {
1917
0
          return d;
1918
0
        }
1919
0
        else
1920
0
        {
1921
0
          d = spheroid_distance(&nearest1, &nearest2, s);
1922
0
          if ( d <= tolerance )
1923
0
            return d;
1924
0
        }
1925
0
      }
1926
1927
      /* Copy end to start to allow a new end value in next iteration */
1928
0
      e2.start = e2.end;
1929
0
      B1 = B2;
1930
0
    }
1931
1932
    /* Copy end to start to allow a new end value in next iteration */
1933
0
    e1.start = e1.end;
1934
0
    A1 = A2;
1935
0
    LW_ON_INTERRUPT(return -1.0);
1936
0
  }
1937
0
  LWDEBUGF(4,"finished all loops, returning %.8g", distance);
1938
1939
0
  if ( use_sphere )
1940
0
    return distance;
1941
0
  else
1942
0
    return spheroid_distance(&nearest1, &nearest2, s);
1943
0
}
1944
1945
1946
/**
1947
* Delegate to the spheroid function with a spherically
1948
* parameterized spheroid.
1949
*/
1950
double lwgeom_area_sphere(const LWGEOM *lwgeom, const SPHEROID *spheroid)
1951
0
{
1952
0
  SPHEROID s;
1953
0
  spheroid_init(&s, WGS84_RADIUS, WGS84_RADIUS);
1954
0
  return lwgeom_area_spheroid(lwgeom, &s);
1955
0
}
1956
1957
1958
/**
1959
* Calculate a projected point given a source point, a distance and a bearing.
1960
* @param r - location of first point.
1961
* @param spheroid - spheroid definition.
1962
* @param distance - distance, in units of the spheroid def'n.
1963
* @param azimuth - azimuth in radians.
1964
* @return s - location of projected point.
1965
*
1966
*/
1967
LWPOINT* lwgeom_project_spheroid(const LWPOINT *r, const SPHEROID *spheroid, double distance, double azimuth)
1968
0
{
1969
0
  GEOGRAPHIC_POINT geo_source, geo_dest;
1970
0
  POINT4D pt_dest;
1971
0
  double x, y;
1972
0
  LWPOINT *lwp;
1973
0
  int has_z, has_m;
1974
1975
  /* Normalize distance to be positive*/
1976
0
  if ( distance < 0.0 ) {
1977
0
    distance = -distance;
1978
0
    azimuth += M_PI;
1979
0
  }
1980
1981
  /* Normalize azimuth */
1982
0
  azimuth -= 2.0 * M_PI * floor(azimuth / (2.0 * M_PI));
1983
1984
  /* Check the distance validity */
1985
0
  if ( distance > (M_PI * spheroid->radius) )
1986
0
  {
1987
0
    lwerror("Distance must not be greater than %g", M_PI * spheroid->radius);
1988
0
    return NULL;
1989
0
  }
1990
1991
  /* Convert to ta geodetic point */
1992
0
  x = lwpoint_get_x(r);
1993
0
  y = lwpoint_get_y(r);
1994
0
  has_z = lwgeom_has_z(lwpoint_as_lwgeom(r));
1995
0
  has_m = lwgeom_has_m(lwpoint_as_lwgeom(r));
1996
0
  geographic_point_init(x, y, &geo_source);
1997
1998
  /* Try the projection */
1999
0
  if( spheroid_project(&geo_source, spheroid, distance, azimuth, &geo_dest) == LW_FAILURE )
2000
0
  {
2001
0
    LWDEBUGF(3, "Unable to project from (%g %g) with azimuth %g and distance %g", x, y, azimuth, distance);
2002
0
    lwerror("Unable to project from (%g %g) with azimuth %g and distance %g", x, y, azimuth, distance);
2003
0
    return NULL;
2004
0
  }
2005
2006
  /* Build the output LWPOINT */
2007
0
  pt_dest.x = rad2deg(longitude_radians_normalize(geo_dest.lon));
2008
0
  pt_dest.y = rad2deg(latitude_radians_normalize(geo_dest.lat));
2009
0
  pt_dest.z = has_z ? lwpoint_get_z(r) : 0.0;
2010
0
  pt_dest.m = has_m ? lwpoint_get_m(r) : 0.0;
2011
0
  lwp = lwpoint_make(r->srid, has_z, has_m, &pt_dest);
2012
0
  lwgeom_set_geodetic(lwpoint_as_lwgeom(lwp), LW_TRUE);
2013
0
  return lwp;
2014
0
}
2015
2016
LWPOINT* lwgeom_project_spheroid_lwpoint(const LWPOINT *from, const LWPOINT *to, const SPHEROID *spheroid, double distance)
2017
0
{
2018
0
  double azimuth = lwgeom_azumith_spheroid(from, to, spheroid);
2019
0
  LWPOINT *lwp = lwgeom_project_spheroid(to, spheroid, distance, azimuth);
2020
0
  return lwp;
2021
0
}
2022
2023
2024
/**
2025
* Calculate a bearing (azimuth) given a source and destination point.
2026
https://accesd.desjardins.ca/coast* @param r - location of first point.
2027
* @param s - location of second point.
2028
* @param spheroid - spheroid definition.
2029
* @return azimuth - azimuth in radians.
2030
*
2031
*/
2032
double lwgeom_azumith_spheroid(const LWPOINT *r, const LWPOINT *s, const SPHEROID *spheroid)
2033
0
{
2034
0
  GEOGRAPHIC_POINT g1, g2;
2035
0
  double x1, y1, x2, y2, az;
2036
2037
  /* Convert r to a geodetic point */
2038
0
  x1 = lwpoint_get_x(r);
2039
0
  y1 = lwpoint_get_y(r);
2040
0
  geographic_point_init(x1, y1, &g1);
2041
2042
  /* Convert s to a geodetic point */
2043
0
  x2 = lwpoint_get_x(s);
2044
0
  y2 = lwpoint_get_y(s);
2045
0
  geographic_point_init(x2, y2, &g2);
2046
2047
  /* Same point, return NaN */
2048
0
  if ( FP_EQUALS(x1, x2) && FP_EQUALS(y1, y2) )
2049
0
  {
2050
0
    return NAN;
2051
0
  }
2052
2053
  /* Do the direction calculation */
2054
0
  az = spheroid_direction(&g1, &g2, spheroid);
2055
  /* Ensure result is positive */
2056
0
  return az < -0 ? 2*M_PI + az : az;
2057
  // return az;
2058
0
}
2059
2060
/**
2061
* Calculate the distance between two LWGEOMs, using the coordinates are
2062
* longitude and latitude. Return immediately when the calculated distance drops
2063
* below the tolerance (useful for dwithin calculations).
2064
* Return a negative distance for incalculable cases.
2065
*/
2066
double lwgeom_distance_spheroid(const LWGEOM *lwgeom1, const LWGEOM *lwgeom2, const SPHEROID *spheroid, double tolerance)
2067
0
{
2068
0
  uint8_t type1, type2;
2069
0
  int check_intersection = LW_FALSE;
2070
0
  GBOX gbox1, gbox2;
2071
2072
0
  gbox_init(&gbox1);
2073
0
  gbox_init(&gbox2);
2074
2075
0
  assert(lwgeom1);
2076
0
  assert(lwgeom2);
2077
2078
0
  LWDEBUGF(4, "entered function, tolerance %.8g", tolerance);
2079
2080
  /* What's the distance to an empty geometry? We don't know.
2081
     Return a negative number so the caller can catch this case. */
2082
0
  if ( lwgeom_is_empty(lwgeom1) || lwgeom_is_empty(lwgeom2) )
2083
0
  {
2084
0
    return -1.0;
2085
0
  }
2086
2087
0
  type1 = lwgeom1->type;
2088
0
  type2 = lwgeom2->type;
2089
2090
  /* Make sure we have boxes */
2091
0
  if ( FLAGS_GET_GEODETIC(lwgeom1->flags) && lwgeom1->bbox )
2092
0
    gbox1 = *(lwgeom1->bbox);
2093
0
  else
2094
0
    lwgeom_calculate_gbox_geodetic(lwgeom1, &gbox1);
2095
2096
  /* Make sure we have boxes */
2097
0
  if ( FLAGS_GET_GEODETIC(lwgeom2->flags) && lwgeom2->bbox )
2098
0
    gbox2 = *(lwgeom2->bbox);
2099
0
  else
2100
0
    lwgeom_calculate_gbox_geodetic(lwgeom2, &gbox2);
2101
2102
  /* If the boxes aren't disjoint, we have to check for edge intersections */
2103
0
  if ( gbox_overlaps(&gbox1, &gbox2) )
2104
0
    check_intersection = LW_TRUE;
2105
2106
  /* Point/line combinations can all be handled with simple point array iterations */
2107
0
  if ( ( type1 == POINTTYPE || type1 == LINETYPE ) &&
2108
0
       ( type2 == POINTTYPE || type2 == LINETYPE ) )
2109
0
  {
2110
0
    POINTARRAY *pa1, *pa2;
2111
2112
0
    if ( type1 == POINTTYPE )
2113
0
      pa1 = ((LWPOINT*)lwgeom1)->point;
2114
0
    else
2115
0
      pa1 = ((LWLINE*)lwgeom1)->points;
2116
2117
0
    if ( type2 == POINTTYPE )
2118
0
      pa2 = ((LWPOINT*)lwgeom2)->point;
2119
0
    else
2120
0
      pa2 = ((LWLINE*)lwgeom2)->points;
2121
2122
0
    return ptarray_distance_spheroid(pa1, pa2, spheroid, tolerance, check_intersection);
2123
0
  }
2124
2125
  /* Point/Polygon cases, if point-in-poly, return zero, else return distance. */
2126
0
  if ( ( type1 == POLYGONTYPE && type2 == POINTTYPE ) ||
2127
0
       ( type2 == POLYGONTYPE && type1 == POINTTYPE ) )
2128
0
  {
2129
0
    const POINT2D *p;
2130
0
    LWPOLY *lwpoly;
2131
0
    LWPOINT *lwpt;
2132
0
    double distance = FLT_MAX;
2133
0
    uint32_t i;
2134
2135
0
    if ( type1 == POINTTYPE )
2136
0
    {
2137
0
      lwpt = (LWPOINT*)lwgeom1;
2138
0
      lwpoly = (LWPOLY*)lwgeom2;
2139
0
    }
2140
0
    else
2141
0
    {
2142
0
      lwpt = (LWPOINT*)lwgeom2;
2143
0
      lwpoly = (LWPOLY*)lwgeom1;
2144
0
    }
2145
0
    p = getPoint2d_cp(lwpt->point, 0);
2146
2147
    /* Point in polygon implies zero distance */
2148
0
    if ( lwpoly_covers_point2d(lwpoly, p) )
2149
0
    {
2150
0
      return 0.0;
2151
0
    }
2152
2153
    /* Not inside, so what's the actual distance? */
2154
0
    for ( i = 0; i < lwpoly->nrings; i++ )
2155
0
    {
2156
0
      double ring_distance = ptarray_distance_spheroid(lwpoly->rings[i], lwpt->point, spheroid, tolerance, check_intersection);
2157
0
      if ( ring_distance < distance )
2158
0
        distance = ring_distance;
2159
0
      if ( distance <= tolerance )
2160
0
        return distance;
2161
0
    }
2162
0
    return distance;
2163
0
  }
2164
2165
  /* Line/polygon case, if start point-in-poly, return zero, else return distance. */
2166
0
  if ( ( type1 == POLYGONTYPE && type2 == LINETYPE ) ||
2167
0
       ( type2 == POLYGONTYPE && type1 == LINETYPE ) )
2168
0
  {
2169
0
    const POINT2D *p;
2170
0
    LWPOLY *lwpoly;
2171
0
    LWLINE *lwline;
2172
0
    double distance = FLT_MAX;
2173
0
    uint32_t i;
2174
2175
0
    if ( type1 == LINETYPE )
2176
0
    {
2177
0
      lwline = (LWLINE*)lwgeom1;
2178
0
      lwpoly = (LWPOLY*)lwgeom2;
2179
0
    }
2180
0
    else
2181
0
    {
2182
0
      lwline = (LWLINE*)lwgeom2;
2183
0
      lwpoly = (LWPOLY*)lwgeom1;
2184
0
    }
2185
0
    p = getPoint2d_cp(lwline->points, 0);
2186
2187
0
    LWDEBUG(4, "checking if a point of line is in polygon");
2188
2189
    /* Point in polygon implies zero distance */
2190
0
    if ( lwpoly_covers_point2d(lwpoly, p) )
2191
0
      return 0.0;
2192
2193
0
    LWDEBUG(4, "checking ring distances");
2194
2195
    /* Not contained, so what's the actual distance? */
2196
0
    for ( i = 0; i < lwpoly->nrings; i++ )
2197
0
    {
2198
0
      double ring_distance = ptarray_distance_spheroid(lwpoly->rings[i], lwline->points, spheroid, tolerance, check_intersection);
2199
0
      LWDEBUGF(4, "ring[%d] ring_distance = %.8g", i, ring_distance);
2200
0
      if ( ring_distance < distance )
2201
0
        distance = ring_distance;
2202
0
      if ( distance <= tolerance )
2203
0
        return distance;
2204
0
    }
2205
0
    LWDEBUGF(4, "all rings checked, returning distance = %.8g", distance);
2206
0
    return distance;
2207
2208
0
  }
2209
2210
  /* Polygon/polygon case, if start point-in-poly, return zero, else
2211
   * return distance. */
2212
0
  if (type1 == POLYGONTYPE && type2 == POLYGONTYPE)
2213
0
  {
2214
0
    const POINT2D* p;
2215
0
    LWPOLY* lwpoly1 = (LWPOLY*)lwgeom1;
2216
0
    LWPOLY* lwpoly2 = (LWPOLY*)lwgeom2;
2217
0
    double distance = FLT_MAX;
2218
0
    uint32_t i, j;
2219
2220
    /* Point of 2 in polygon 1 implies zero distance */
2221
0
    p = getPoint2d_cp(lwpoly1->rings[0], 0);
2222
0
    if (lwpoly_covers_point2d(lwpoly2, p)) return 0.0;
2223
2224
    /* Point of 1 in polygon 2 implies zero distance */
2225
0
    p = getPoint2d_cp(lwpoly2->rings[0], 0);
2226
0
    if (lwpoly_covers_point2d(lwpoly1, p)) return 0.0;
2227
2228
    /* Not contained, so what's the actual distance? */
2229
0
    for (i = 0; i < lwpoly1->nrings; i++)
2230
0
    {
2231
0
      for (j = 0; j < lwpoly2->nrings; j++)
2232
0
      {
2233
0
        double ring_distance =
2234
0
            ptarray_distance_spheroid(
2235
0
          lwpoly1->rings[i],
2236
0
          lwpoly2->rings[j],
2237
0
          spheroid,
2238
0
          tolerance,
2239
0
          check_intersection);
2240
0
        if (ring_distance < distance)
2241
0
          distance = ring_distance;
2242
0
        if (distance <= tolerance) return distance;
2243
0
      }
2244
0
    }
2245
0
    return distance;
2246
0
  }
2247
2248
  /* Recurse into collections */
2249
0
  if ( lwtype_is_collection(type1) )
2250
0
  {
2251
0
    uint32_t i;
2252
0
    double distance = FLT_MAX;
2253
0
    LWCOLLECTION *col = (LWCOLLECTION*)lwgeom1;
2254
2255
0
    for ( i = 0; i < col->ngeoms; i++ )
2256
0
    {
2257
0
      double geom_distance = lwgeom_distance_spheroid(
2258
0
          col->geoms[i], lwgeom2, spheroid, tolerance);
2259
0
      if ( geom_distance < distance )
2260
0
        distance = geom_distance;
2261
0
      if ( distance <= tolerance )
2262
0
        return distance;
2263
0
    }
2264
0
    return distance;
2265
0
  }
2266
2267
  /* Recurse into collections */
2268
0
  if ( lwtype_is_collection(type2) )
2269
0
  {
2270
0
    uint32_t i;
2271
0
    double distance = FLT_MAX;
2272
0
    LWCOLLECTION *col = (LWCOLLECTION*)lwgeom2;
2273
2274
0
    for ( i = 0; i < col->ngeoms; i++ )
2275
0
    {
2276
0
      double geom_distance = lwgeom_distance_spheroid(lwgeom1, col->geoms[i], spheroid, tolerance);
2277
0
      if ( geom_distance < distance )
2278
0
        distance = geom_distance;
2279
0
      if ( distance <= tolerance )
2280
0
        return distance;
2281
0
    }
2282
0
    return distance;
2283
0
  }
2284
2285
2286
0
  lwerror("arguments include unsupported geometry type (%s, %s)", lwtype_name(type1), lwtype_name(type1));
2287
0
  return -1.0;
2288
2289
0
}
2290
2291
2292
int lwgeom_covers_lwgeom_sphere(const LWGEOM *lwgeom1, const LWGEOM *lwgeom2)
2293
0
{
2294
0
  int type1, type2;
2295
0
  GBOX gbox1, gbox2;
2296
0
  gbox1.flags = gbox2.flags = 0;
2297
2298
0
  assert(lwgeom1);
2299
0
  assert(lwgeom2);
2300
2301
0
  type1 = lwgeom1->type;
2302
0
  type2 = lwgeom2->type;
2303
2304
  /* dim(geom2) > dim(geom1) always returns false (because geom2 is bigger) */
2305
0
  if ( (type1 == POINTTYPE && type2 == LINETYPE)
2306
0
    || (type1 == POINTTYPE && type2 == POLYGONTYPE)
2307
0
    || (type1 == LINETYPE && type2 == POLYGONTYPE) )
2308
0
  {
2309
0
    LWDEBUG(4, "dimension of geom2 is bigger than geom1");
2310
0
    return LW_FALSE;
2311
0
  }
2312
2313
  /* Make sure we have boxes */
2314
0
  if ( lwgeom1->bbox )
2315
0
    gbox1 = *(lwgeom1->bbox);
2316
0
  else
2317
0
    lwgeom_calculate_gbox_geodetic(lwgeom1, &gbox1);
2318
2319
  /* Make sure we have boxes */
2320
0
  if ( lwgeom2->bbox )
2321
0
    gbox2 = *(lwgeom2->bbox);
2322
0
  else
2323
0
    lwgeom_calculate_gbox_geodetic(lwgeom2, &gbox2);
2324
2325
2326
  /* Handle the polygon/point case */
2327
0
  if ( type1 == POLYGONTYPE && type2 == POINTTYPE )
2328
0
  {
2329
0
    POINT2D pt_to_test;
2330
0
    getPoint2d_p(((LWPOINT*)lwgeom2)->point, 0, &pt_to_test);
2331
0
    return lwpoly_covers_point2d((LWPOLY*)lwgeom1, &pt_to_test);
2332
0
  }
2333
0
  else if ( type1 == POLYGONTYPE && type2 == LINETYPE)
2334
0
  {
2335
0
    return lwpoly_covers_lwline((LWPOLY*)lwgeom1, (LWLINE*)lwgeom2);
2336
0
  }
2337
0
  else if ( type1 == POLYGONTYPE && type2 == POLYGONTYPE)
2338
0
  {
2339
0
    return lwpoly_covers_lwpoly((LWPOLY*)lwgeom1, (LWPOLY*)lwgeom2);
2340
0
  }
2341
0
  else if ( type1 == LINETYPE && type2 == POINTTYPE)
2342
0
  {
2343
0
    return lwline_covers_lwpoint((LWLINE*)lwgeom1, (LWPOINT*)lwgeom2);
2344
0
  }
2345
0
  else if ( type1 == LINETYPE && type2 == LINETYPE)
2346
0
  {
2347
0
    return lwline_covers_lwline((LWLINE*)lwgeom1, (LWLINE*)lwgeom2);
2348
0
  }
2349
0
  else if ( type1 == POINTTYPE && type2 == POINTTYPE)
2350
0
  {
2351
0
    return lwpoint_same((LWPOINT*)lwgeom1, (LWPOINT*)lwgeom2);
2352
0
  }
2353
2354
  /* If any of the first argument parts covers the second argument, it's true */
2355
0
  if ( lwtype_is_collection( type1 ) )
2356
0
  {
2357
0
    uint32_t i;
2358
0
    LWCOLLECTION *col = (LWCOLLECTION*)lwgeom1;
2359
2360
0
    for ( i = 0; i < col->ngeoms; i++ )
2361
0
    {
2362
0
      if ( lwgeom_covers_lwgeom_sphere(col->geoms[i], lwgeom2) )
2363
0
      {
2364
0
        return LW_TRUE;
2365
0
      }
2366
0
    }
2367
0
    return LW_FALSE;
2368
0
  }
2369
2370
  /* Only if all of the second arguments are covered by the first argument is the condition true */
2371
0
  if ( lwtype_is_collection( type2 ) )
2372
0
  {
2373
0
    uint32_t i;
2374
0
    LWCOLLECTION *col = (LWCOLLECTION*)lwgeom2;
2375
2376
0
    for ( i = 0; i < col->ngeoms; i++ )
2377
0
    {
2378
0
      if ( ! lwgeom_covers_lwgeom_sphere(lwgeom1, col->geoms[i]) )
2379
0
      {
2380
0
        return LW_FALSE;
2381
0
      }
2382
0
    }
2383
0
    return LW_TRUE;
2384
0
  }
2385
2386
  /* Don't get here */
2387
0
  lwerror("lwgeom_covers_lwgeom_sphere: reached end of function without resolution");
2388
0
  return LW_FALSE;
2389
2390
0
}
2391
2392
/**
2393
* Given a polygon (lon/lat decimal degrees) and point (lon/lat decimal degrees) and
2394
* a guaranteed outside point (lon/lat decimal degrees) (calculate with gbox_pt_outside())
2395
* return LW_TRUE if point is inside or on edge of polygon.
2396
*/
2397
int lwpoly_covers_point2d(const LWPOLY *poly, const POINT2D *pt_to_test)
2398
0
{
2399
0
  uint32_t i;
2400
0
  int in_hole_count = 0;
2401
0
  POINT3D p;
2402
0
  GEOGRAPHIC_POINT gpt_to_test;
2403
0
  POINT2D pt_outside;
2404
0
  GBOX gbox;
2405
#if POSTGIS_DEBUG_LEVEL >= 4
2406
  char *geom_ewkt;
2407
#endif
2408
0
  gbox.flags = 0;
2409
2410
  /* Nulls and empties don't contain anything! */
2411
0
  if ( ! poly || lwgeom_is_empty((LWGEOM*)poly) )
2412
0
  {
2413
0
    LWDEBUG(4,"returning false, geometry is empty or null");
2414
0
    return LW_FALSE;
2415
0
  }
2416
2417
  /* Make sure we have boxes */
2418
0
  if ( poly->bbox )
2419
0
    gbox = *(poly->bbox);
2420
0
  else
2421
0
    lwgeom_calculate_gbox_geodetic((LWGEOM*)poly, &gbox);
2422
2423
  /* Point not in box? Done! */
2424
0
  geographic_point_init(pt_to_test->x, pt_to_test->y, &gpt_to_test);
2425
0
  geog2cart(&gpt_to_test, &p);
2426
0
  if ( ! gbox_contains_point3d(&gbox, &p) )
2427
0
  {
2428
0
    LWDEBUG(4, "the point is not in the box!");
2429
0
    return LW_FALSE;
2430
0
  }
2431
2432
  /* Calculate our outside point from the gbox */
2433
0
  lwpoly_pt_outside(poly, &pt_outside);
2434
2435
0
  LWDEBUGF(4, "pt_outside POINT(%.18g %.18g)", pt_outside.x, pt_outside.y);
2436
0
  LWDEBUGF(4, "pt_to_test POINT(%.18g %.18g)", pt_to_test->x, pt_to_test->y);
2437
#if POSTGIS_DEBUG_LEVEL >= 4
2438
  geom_ewkt = lwgeom_to_ewkt((LWGEOM*)poly);
2439
  LWDEBUGF(4, "polygon %s", geom_ewkt);
2440
  lwfree(geom_ewkt);
2441
  geom_ewkt = gbox_to_string(&gbox);
2442
  LWDEBUGF(4, "gbox %s", geom_ewkt);
2443
  lwfree(geom_ewkt);
2444
#endif
2445
2446
  /* Not in outer ring? We're done! */
2447
0
  if ( ! ptarray_contains_point_sphere(poly->rings[0], &pt_outside, pt_to_test) )
2448
0
  {
2449
0
    LWDEBUG(4,"returning false, point is outside ring");
2450
0
    return LW_FALSE;
2451
0
  }
2452
2453
0
  LWDEBUGF(4, "testing %d rings", poly->nrings);
2454
2455
  /* But maybe point is in a hole... */
2456
0
  for ( i = 1; i < poly->nrings; i++ )
2457
0
  {
2458
0
    LWDEBUGF(4, "ring test loop %d", i);
2459
    /* Count up hole containment. Odd => outside boundary. */
2460
0
    if ( ptarray_contains_point_sphere(poly->rings[i], &pt_outside, pt_to_test) )
2461
0
      in_hole_count++;
2462
0
  }
2463
2464
0
  LWDEBUGF(4, "in_hole_count == %d", in_hole_count);
2465
2466
0
  if ( in_hole_count % 2 )
2467
0
  {
2468
0
    LWDEBUG(4,"returning false, inner ring containment count is odd");
2469
0
    return LW_FALSE;
2470
0
  }
2471
2472
0
  LWDEBUG(4,"returning true, inner ring containment count is even");
2473
0
  return LW_TRUE;
2474
0
}
2475
2476
/**
2477
 * Given a polygon1 check if all points of polygon2 are inside polygon1 and no
2478
 * intersections of the polygon edges occur.
2479
 * return LW_TRUE if polygon is inside or on edge of polygon.
2480
 */
2481
int lwpoly_covers_lwpoly(const LWPOLY *poly1, const LWPOLY *poly2)
2482
0
{
2483
0
  uint32_t i;
2484
2485
  /* Nulls and empties don't contain anything! */
2486
0
  if ( ! poly1 || lwgeom_is_empty((LWGEOM*)poly1) )
2487
0
  {
2488
0
    LWDEBUG(4,"returning false, geometry1 is empty or null");
2489
0
    return LW_FALSE;
2490
0
  }
2491
2492
  /* Nulls and empties don't contain anything! */
2493
0
  if ( ! poly2 || lwgeom_is_empty((LWGEOM*)poly2) )
2494
0
  {
2495
0
    LWDEBUG(4,"returning false, geometry2 is empty or null");
2496
0
    return LW_FALSE;
2497
0
  }
2498
2499
  /* check if all vertices of poly2 are inside poly1 */
2500
0
  for (i = 0; i < poly2->nrings; i++)
2501
0
  {
2502
0
    if (LW_FALSE == lwpoly_covers_pointarray(poly1, poly2->rings[i]))
2503
0
    {
2504
0
      LWDEBUG(4,"returning false, geometry2 has point outside of geometry1");
2505
0
      return LW_FALSE;
2506
0
    }
2507
0
  }
2508
2509
  /* check for any edge intersections, so nothing is partially outside of poly1 */
2510
0
  for (i = 0; i < poly2->nrings; i++)
2511
0
  {
2512
0
    if (LW_TRUE == lwpoly_intersects_line(poly1, poly2->rings[i]))
2513
0
    {
2514
0
      LWDEBUG(4,"returning false, geometry2 is partially outside of geometry1");
2515
0
      return LW_FALSE;
2516
0
    }
2517
0
  }
2518
2519
  /* no abort condition found, so the poly2 should be completely inside poly1 */
2520
0
  return LW_TRUE;
2521
0
}
2522
2523
/**
2524
 *
2525
 */
2526
int lwpoly_covers_lwline(const LWPOLY *poly, const LWLINE *line)
2527
0
{
2528
   /* Nulls and empties don't contain anything! */
2529
0
   if ( ! poly || lwgeom_is_empty((LWGEOM*)poly) )
2530
0
   {
2531
0
     LWDEBUG(4,"returning false, geometry1 is empty or null");
2532
0
     return LW_FALSE;
2533
0
   }
2534
2535
   /* Nulls and empties don't contain anything! */
2536
0
   if ( ! line || lwgeom_is_empty((LWGEOM*)line) )
2537
0
   {
2538
0
     LWDEBUG(4,"returning false, geometry2 is empty or null");
2539
0
     return LW_FALSE;
2540
0
   }
2541
2542
0
   if (LW_FALSE == lwpoly_covers_pointarray(poly, line->points))
2543
0
   {
2544
0
     LWDEBUG(4,"returning false, geometry2 has point outside of geometry1");
2545
0
     return LW_FALSE;
2546
0
   }
2547
2548
   /* check for any edge intersections, so nothing is partially outside of poly1 */
2549
0
   if (LW_TRUE == lwpoly_intersects_line(poly, line->points))
2550
0
   {
2551
0
     LWDEBUG(4,"returning false, geometry2 is partially outside of geometry1");
2552
0
     return LW_FALSE;
2553
0
   }
2554
2555
   /* no abort condition found, so the poly2 should be completely inside poly1 */
2556
0
   return LW_TRUE;
2557
0
}
2558
2559
/**
2560
 * return LW_TRUE if all points are inside the polygon
2561
 */
2562
int lwpoly_covers_pointarray(const LWPOLY* lwpoly, const POINTARRAY* pta)
2563
0
{
2564
0
  uint32_t i;
2565
0
  for (i = 0; i < pta->npoints; i++) {
2566
0
    const POINT2D* pt_to_test = getPoint2d_cp(pta, i);
2567
2568
0
    if ( LW_FALSE == lwpoly_covers_point2d(lwpoly, pt_to_test) ) {
2569
0
      LWDEBUG(4,"returning false, geometry2 has point outside of geometry1");
2570
0
      return LW_FALSE;
2571
0
    }
2572
0
  }
2573
2574
0
  return LW_TRUE;
2575
0
}
2576
2577
/**
2578
 * Checks if any edges of lwpoly intersect with the line formed by the pointarray
2579
 * return LW_TRUE if any intersection between the given polygon and the line
2580
 */
2581
int lwpoly_intersects_line(const LWPOLY* lwpoly, const POINTARRAY* line)
2582
0
{
2583
0
  uint32_t i, j, k;
2584
0
  POINT3D pa1, pa2, pb1, pb2;
2585
0
  for (i = 0; i < lwpoly->nrings; i++)
2586
0
  {
2587
0
    for (j = 0; j < lwpoly->rings[i]->npoints - 1; j++)
2588
0
    {
2589
0
      const POINT2D* a1 = getPoint2d_cp(lwpoly->rings[i], j);
2590
0
      const POINT2D* a2 = getPoint2d_cp(lwpoly->rings[i], j+1);
2591
2592
      /* Set up our stab line */
2593
0
      ll2cart(a1, &pa1);
2594
0
      ll2cart(a2, &pa2);
2595
2596
0
      for (k = 0; k < line->npoints - 1; k++)
2597
0
      {
2598
0
        const POINT2D* b1 = getPoint2d_cp(line, k);
2599
0
        const POINT2D* b2 = getPoint2d_cp(line, k+1);
2600
2601
        /* Set up our stab line */
2602
0
        ll2cart(b1, &pb1);
2603
0
        ll2cart(b2, &pb2);
2604
2605
0
        int inter = edge_intersects(&pa1, &pa2, &pb1, &pb2);
2606
2607
        /* ignore same edges */
2608
0
        if (inter & PIR_INTERSECTS
2609
0
          && !(inter & PIR_B_TOUCH_RIGHT || inter & PIR_COLINEAR) )
2610
0
        {
2611
0
          return LW_TRUE;
2612
0
        }
2613
0
      }
2614
0
    }
2615
0
  }
2616
2617
0
  return LW_FALSE;
2618
0
}
2619
2620
/**
2621
 * return LW_TRUE if any of the line segments covers the point
2622
 */
2623
int lwline_covers_lwpoint(const LWLINE* lwline, const LWPOINT* lwpoint)
2624
0
{
2625
0
  uint32_t i;
2626
0
  GEOGRAPHIC_POINT p;
2627
0
  GEOGRAPHIC_EDGE e;
2628
2629
0
  for ( i = 0; i < lwline->points->npoints - 1; i++)
2630
0
  {
2631
0
    const POINT2D* a1 = getPoint2d_cp(lwline->points, i);
2632
0
    const POINT2D* a2 = getPoint2d_cp(lwline->points, i+1);
2633
2634
0
    geographic_point_init(a1->x, a1->y, &(e.start));
2635
0
    geographic_point_init(a2->x, a2->y, &(e.end));
2636
2637
0
    geographic_point_init(lwpoint_get_x(lwpoint), lwpoint_get_y(lwpoint), &p);
2638
2639
0
    if ( edge_contains_point(&e, &p) ) {
2640
0
      return LW_TRUE;
2641
0
    }
2642
0
  }
2643
2644
0
  return LW_FALSE;
2645
0
}
2646
2647
/**
2648
 * Check if first and last point of line2 are covered by line1 and then each
2649
 * point in between has to be one line1 in the exact same order
2650
 * return LW_TRUE if all edge points of line2 are on line1
2651
 */
2652
int lwline_covers_lwline(const LWLINE* lwline1, const LWLINE* lwline2)
2653
0
{
2654
0
  uint32_t i, j;
2655
0
  GEOGRAPHIC_EDGE e1, e2;
2656
0
  GEOGRAPHIC_POINT p1, p2;
2657
0
  int start = LW_FALSE;
2658
0
  int changed = LW_FALSE;
2659
2660
  /* first point on line */
2661
0
  if ( ! lwline_covers_lwpoint(lwline1, lwline_get_lwpoint(lwline2, 0)))
2662
0
  {
2663
0
    LWDEBUG(4,"returning false, first point of line2 is not covered by line1");
2664
0
    return LW_FALSE;
2665
0
  }
2666
2667
  /* last point on line */
2668
0
  if ( ! lwline_covers_lwpoint(lwline1, lwline_get_lwpoint(lwline2, lwline2->points->npoints - 1)))
2669
0
  {
2670
0
    LWDEBUG(4,"returning false, last point of line2 is not covered by line1");
2671
0
    return LW_FALSE;
2672
0
  }
2673
2674
0
  j = 0;
2675
0
  i = 0;
2676
0
  while (i < lwline1->points->npoints - 1 && j < lwline2->points->npoints - 1)
2677
0
  {
2678
0
    changed = LW_FALSE;
2679
0
    const POINT2D* a1 = getPoint2d_cp(lwline1->points, i);
2680
0
    const POINT2D* a2 = getPoint2d_cp(lwline1->points, i+1);
2681
0
    const POINT2D* b1 = getPoint2d_cp(lwline2->points, j);
2682
0
    const POINT2D* b2 = getPoint2d_cp(lwline2->points, j+1);
2683
2684
0
    geographic_point_init(a1->x, a1->y, &(e1.start));
2685
0
    geographic_point_init(a2->x, a2->y, &(e1.end));
2686
0
    geographic_point_init(b1->x, b1->y, &p2);
2687
2688
    /* we already know, that the last point is on line1, so we're done */
2689
0
    if ( j == lwline2->points->npoints - 1)
2690
0
    {
2691
0
      return LW_TRUE;
2692
0
    }
2693
0
    else if (start == LW_TRUE)
2694
0
    {
2695
      /* point is on current line1 edge, check next point in line2 */
2696
0
      if ( edge_contains_point(&e1, &p2)) {
2697
0
        j++;
2698
0
        changed = LW_TRUE;
2699
0
      }
2700
2701
0
      geographic_point_init(a1->x, a1->y, &(e2.start));
2702
0
      geographic_point_init(a2->x, b2->y, &(e2.end));
2703
0
      geographic_point_init(a1->x, a1->y, &p1);
2704
2705
      /* point is on current line2 edge, check next point in line1 */
2706
0
      if ( edge_contains_point(&e2, &p1)) {
2707
0
        i++;
2708
0
        changed = LW_TRUE;
2709
0
      }
2710
2711
      /* no edge progressed -> point left one line */
2712
0
      if ( changed == LW_FALSE )
2713
0
      {
2714
0
        LWDEBUG(4,"returning false, found point not covered by both lines");
2715
0
        return LW_FALSE;
2716
0
      }
2717
0
      else
2718
0
      {
2719
0
        continue;
2720
0
      }
2721
0
    }
2722
2723
    /* find first edge to cover line2 */
2724
0
    if (edge_contains_point(&e1, &p2))
2725
0
    {
2726
0
      start = LW_TRUE;
2727
0
    }
2728
2729
    /* next line1 edge */
2730
0
    i++;
2731
0
  }
2732
2733
  /* no uncovered point found */
2734
0
  return LW_TRUE;
2735
0
}
2736
2737
int ptarray_calculate_gbox_geodetic(const POINTARRAY *pa, GBOX *gbox)
2738
0
{
2739
0
  uint32_t i;
2740
0
  int first = LW_TRUE;
2741
0
  const POINT2D *p;
2742
0
  POINT3D A1, A2;
2743
0
  GBOX edge_gbox;
2744
2745
0
  assert(gbox);
2746
0
  assert(pa);
2747
2748
0
  gbox_init(&edge_gbox);
2749
0
  edge_gbox.flags = gbox->flags;
2750
2751
0
  if ( pa->npoints == 0 ) return LW_FAILURE;
2752
2753
0
  if ( pa->npoints == 1 )
2754
0
  {
2755
0
    p = getPoint2d_cp(pa, 0);
2756
0
    ll2cart(p, &A1);
2757
0
    gbox->xmin = gbox->xmax = A1.x;
2758
0
    gbox->ymin = gbox->ymax = A1.y;
2759
0
    gbox->zmin = gbox->zmax = A1.z;
2760
0
    return LW_SUCCESS;
2761
0
  }
2762
2763
0
  p = getPoint2d_cp(pa, 0);
2764
0
  ll2cart(p, &A1);
2765
2766
0
  for ( i = 1; i < pa->npoints; i++ )
2767
0
  {
2768
2769
0
    p = getPoint2d_cp(pa, i);
2770
0
    ll2cart(p, &A2);
2771
2772
0
    edge_calculate_gbox(&A1, &A2, &edge_gbox);
2773
2774
    /* Initialize the box */
2775
0
    if ( first )
2776
0
    {
2777
0
      gbox_duplicate(&edge_gbox, gbox);
2778
0
      first = LW_FALSE;
2779
0
    }
2780
    /* Expand the box where necessary */
2781
0
    else
2782
0
    {
2783
0
      gbox_merge(&edge_gbox, gbox);
2784
0
    }
2785
2786
0
    A1 = A2;
2787
0
  }
2788
2789
0
  return LW_SUCCESS;
2790
0
}
2791
2792
static int lwpoint_calculate_gbox_geodetic(const LWPOINT *point, GBOX *gbox)
2793
0
{
2794
0
  assert(point);
2795
0
  return ptarray_calculate_gbox_geodetic(point->point, gbox);
2796
0
}
2797
2798
static int lwline_calculate_gbox_geodetic(const LWLINE *line, GBOX *gbox)
2799
0
{
2800
0
  assert(line);
2801
0
  return ptarray_calculate_gbox_geodetic(line->points, gbox);
2802
0
}
2803
2804
static int lwpolygon_calculate_gbox_geodetic(const LWPOLY *poly, GBOX *gbox)
2805
0
{
2806
0
  GBOX ringbox;
2807
0
  uint32_t i;
2808
0
  int first = LW_TRUE;
2809
0
  assert(poly);
2810
0
  if ( poly->nrings == 0 )
2811
0
    return LW_FAILURE;
2812
0
  ringbox.flags = gbox->flags;
2813
0
  for ( i = 0; i < poly->nrings; i++ )
2814
0
  {
2815
0
    if ( ptarray_calculate_gbox_geodetic(poly->rings[i], &ringbox) == LW_FAILURE )
2816
0
      return LW_FAILURE;
2817
0
    if ( first )
2818
0
    {
2819
0
      gbox_duplicate(&ringbox, gbox);
2820
0
      first = LW_FALSE;
2821
0
    }
2822
0
    else
2823
0
    {
2824
0
      gbox_merge(&ringbox, gbox);
2825
0
    }
2826
0
  }
2827
2828
  /* If the box wraps a poly, push that axis to the absolute min/max as appropriate */
2829
0
  gbox_check_poles(gbox);
2830
2831
0
  return LW_SUCCESS;
2832
0
}
2833
2834
static int lwtriangle_calculate_gbox_geodetic(const LWTRIANGLE *triangle, GBOX *gbox)
2835
0
{
2836
0
  assert(triangle);
2837
0
  return ptarray_calculate_gbox_geodetic(triangle->points, gbox);
2838
0
}
2839
2840
2841
static int lwcollection_calculate_gbox_geodetic(const LWCOLLECTION *coll, GBOX *gbox)
2842
0
{
2843
0
  GBOX subbox = {0};
2844
0
  uint32_t i;
2845
0
  int result = LW_FAILURE;
2846
0
  int first = LW_TRUE;
2847
0
  assert(coll);
2848
0
  if ( coll->ngeoms == 0 )
2849
0
    return LW_FAILURE;
2850
2851
0
  subbox.flags = gbox->flags;
2852
2853
0
  for ( i = 0; i < coll->ngeoms; i++ )
2854
0
  {
2855
0
    if ( lwgeom_calculate_gbox_geodetic((LWGEOM*)(coll->geoms[i]), &subbox) == LW_SUCCESS )
2856
0
    {
2857
      /* Keep a copy of the sub-bounding box for later */
2858
0
      if ( coll->geoms[i]->bbox )
2859
0
        lwfree(coll->geoms[i]->bbox);
2860
0
      coll->geoms[i]->bbox = gbox_copy(&subbox);
2861
0
      if ( first )
2862
0
      {
2863
0
        gbox_duplicate(&subbox, gbox);
2864
0
        first = LW_FALSE;
2865
0
      }
2866
0
      else
2867
0
      {
2868
0
        gbox_merge(&subbox, gbox);
2869
0
      }
2870
0
      result = LW_SUCCESS;
2871
0
    }
2872
0
  }
2873
0
  return result;
2874
0
}
2875
2876
int lwgeom_calculate_gbox_geodetic(const LWGEOM *geom, GBOX *gbox)
2877
0
{
2878
0
  int result = LW_FAILURE;
2879
0
  LWDEBUGF(4, "got type %d", geom->type);
2880
2881
  /* Add a geodetic flag to the incoming gbox */
2882
0
  gbox->flags = lwflags(FLAGS_GET_Z(geom->flags),FLAGS_GET_M(geom->flags),1);
2883
2884
0
  switch (geom->type)
2885
0
  {
2886
0
  case POINTTYPE:
2887
0
    result = lwpoint_calculate_gbox_geodetic((LWPOINT*)geom, gbox);
2888
0
    break;
2889
0
  case LINETYPE:
2890
0
    result = lwline_calculate_gbox_geodetic((LWLINE *)geom, gbox);
2891
0
    break;
2892
0
  case POLYGONTYPE:
2893
0
    result = lwpolygon_calculate_gbox_geodetic((LWPOLY *)geom, gbox);
2894
0
    break;
2895
0
  case TRIANGLETYPE:
2896
0
    result = lwtriangle_calculate_gbox_geodetic((LWTRIANGLE *)geom, gbox);
2897
0
    break;
2898
0
  case MULTIPOINTTYPE:
2899
0
  case MULTILINETYPE:
2900
0
  case MULTIPOLYGONTYPE:
2901
0
  case POLYHEDRALSURFACETYPE:
2902
0
  case TINTYPE:
2903
0
  case COLLECTIONTYPE:
2904
0
    result = lwcollection_calculate_gbox_geodetic((LWCOLLECTION *)geom, gbox);
2905
0
    break;
2906
0
  default:
2907
0
    lwerror("lwgeom_calculate_gbox_geodetic: unsupported input geometry type: %d - %s",
2908
0
            geom->type, lwtype_name(geom->type));
2909
0
    break;
2910
0
  }
2911
0
  return result;
2912
0
}
2913
2914
2915
2916
static int ptarray_check_geodetic(const POINTARRAY *pa)
2917
0
{
2918
0
  uint32_t t;
2919
0
  POINT2D pt;
2920
2921
0
  assert(pa);
2922
2923
0
  for (t=0; t<pa->npoints; t++)
2924
0
  {
2925
0
    getPoint2d_p(pa, t, &pt);
2926
    /* printf( "%d (%g, %g)\n", t, pt.x, pt.y); */
2927
0
    if ( pt.x < -180.0 || pt.y < -90.0 || pt.x > 180.0 || pt.y > 90.0 )
2928
0
      return LW_FALSE;
2929
0
  }
2930
2931
0
  return LW_TRUE;
2932
0
}
2933
2934
static int lwpoint_check_geodetic(const LWPOINT *point)
2935
0
{
2936
0
  assert(point);
2937
0
  return ptarray_check_geodetic(point->point);
2938
0
}
2939
2940
static int lwline_check_geodetic(const LWLINE *line)
2941
0
{
2942
0
  assert(line);
2943
0
  return ptarray_check_geodetic(line->points);
2944
0
}
2945
2946
static int lwpoly_check_geodetic(const LWPOLY *poly)
2947
0
{
2948
0
  uint32_t i = 0;
2949
0
  assert(poly);
2950
2951
0
  for ( i = 0; i < poly->nrings; i++ )
2952
0
  {
2953
0
    if ( ptarray_check_geodetic(poly->rings[i]) == LW_FALSE )
2954
0
      return LW_FALSE;
2955
0
  }
2956
0
  return LW_TRUE;
2957
0
}
2958
2959
static int lwtriangle_check_geodetic(const LWTRIANGLE *triangle)
2960
0
{
2961
0
  assert(triangle);
2962
0
  return ptarray_check_geodetic(triangle->points);
2963
0
}
2964
2965
2966
static int lwcollection_check_geodetic(const LWCOLLECTION *col)
2967
0
{
2968
0
  uint32_t i = 0;
2969
0
  assert(col);
2970
2971
0
  for ( i = 0; i < col->ngeoms; i++ )
2972
0
  {
2973
0
    if ( lwgeom_check_geodetic(col->geoms[i]) == LW_FALSE )
2974
0
      return LW_FALSE;
2975
0
  }
2976
0
  return LW_TRUE;
2977
0
}
2978
2979
int lwgeom_check_geodetic(const LWGEOM *geom)
2980
0
{
2981
0
  if ( lwgeom_is_empty(geom) )
2982
0
    return LW_TRUE;
2983
2984
0
  switch (geom->type)
2985
0
  {
2986
0
  case POINTTYPE:
2987
0
    return lwpoint_check_geodetic((LWPOINT *)geom);
2988
0
  case LINETYPE:
2989
0
    return lwline_check_geodetic((LWLINE *)geom);
2990
0
  case POLYGONTYPE:
2991
0
    return lwpoly_check_geodetic((LWPOLY *)geom);
2992
0
  case TRIANGLETYPE:
2993
0
    return lwtriangle_check_geodetic((LWTRIANGLE *)geom);
2994
0
  case MULTIPOINTTYPE:
2995
0
  case MULTILINETYPE:
2996
0
  case MULTIPOLYGONTYPE:
2997
0
  case POLYHEDRALSURFACETYPE:
2998
0
  case TINTYPE:
2999
0
  case COLLECTIONTYPE:
3000
0
    return lwcollection_check_geodetic((LWCOLLECTION *)geom);
3001
0
  default:
3002
0
    lwerror("lwgeom_check_geodetic: unsupported input geometry type: %d - %s",
3003
0
            geom->type, lwtype_name(geom->type));
3004
0
  }
3005
0
  return LW_FALSE;
3006
0
}
3007
3008
static int ptarray_force_geodetic(POINTARRAY *pa)
3009
0
{
3010
0
  uint32_t t;
3011
0
  int changed = LW_FALSE;
3012
0
  POINT4D pt;
3013
3014
0
  assert(pa);
3015
3016
0
  for ( t=0; t < pa->npoints; t++ )
3017
0
  {
3018
0
    getPoint4d_p(pa, t, &pt);
3019
0
    if ( pt.x < -180.0 || pt.x > 180.0 || pt.y < -90.0 || pt.y > 90.0 )
3020
0
    {
3021
0
      pt.x = longitude_degrees_normalize(pt.x);
3022
0
      pt.y = latitude_degrees_normalize(pt.y);
3023
0
      ptarray_set_point4d(pa, t, &pt);
3024
0
      changed = LW_TRUE;
3025
0
    }
3026
0
  }
3027
0
  return changed;
3028
0
}
3029
3030
static int lwpoint_force_geodetic(LWPOINT *point)
3031
0
{
3032
0
  assert(point);
3033
0
  return ptarray_force_geodetic(point->point);
3034
0
}
3035
3036
static int lwline_force_geodetic(LWLINE *line)
3037
0
{
3038
0
  assert(line);
3039
0
  return ptarray_force_geodetic(line->points);
3040
0
}
3041
3042
static int lwpoly_force_geodetic(LWPOLY *poly)
3043
0
{
3044
0
  uint32_t i = 0;
3045
0
  int changed = LW_FALSE;
3046
0
  assert(poly);
3047
3048
0
  for ( i = 0; i < poly->nrings; i++ )
3049
0
  {
3050
0
    if ( ptarray_force_geodetic(poly->rings[i]) == LW_TRUE )
3051
0
      changed = LW_TRUE;
3052
0
  }
3053
0
  return changed;
3054
0
}
3055
3056
static int lwcollection_force_geodetic(LWCOLLECTION *col)
3057
0
{
3058
0
  uint32_t i = 0;
3059
0
  int changed = LW_FALSE;
3060
0
  assert(col);
3061
3062
0
  for ( i = 0; i < col->ngeoms; i++ )
3063
0
  {
3064
0
    if ( lwgeom_force_geodetic(col->geoms[i]) == LW_TRUE )
3065
0
      changed = LW_TRUE;
3066
0
  }
3067
0
  return changed;
3068
0
}
3069
3070
int lwgeom_force_geodetic(LWGEOM *geom)
3071
0
{
3072
0
  switch ( lwgeom_get_type(geom) )
3073
0
  {
3074
0
    case POINTTYPE:
3075
0
      return lwpoint_force_geodetic((LWPOINT *)geom);
3076
0
    case LINETYPE:
3077
0
      return lwline_force_geodetic((LWLINE *)geom);
3078
0
    case POLYGONTYPE:
3079
0
      return lwpoly_force_geodetic((LWPOLY *)geom);
3080
0
    case MULTIPOINTTYPE:
3081
0
    case MULTILINETYPE:
3082
0
    case MULTIPOLYGONTYPE:
3083
0
    case COLLECTIONTYPE:
3084
0
      return lwcollection_force_geodetic((LWCOLLECTION *)geom);
3085
0
    default:
3086
0
      lwerror("unsupported input geometry type: %d", lwgeom_get_type(geom));
3087
0
  }
3088
0
  return LW_FALSE;
3089
0
}
3090
3091
3092
double ptarray_length_spheroid(const POINTARRAY *pa, const SPHEROID *s)
3093
0
{
3094
0
  GEOGRAPHIC_POINT a, b;
3095
0
  double za = 0.0, zb = 0.0;
3096
0
  POINT4D p;
3097
0
  uint32_t i;
3098
0
  int hasz = LW_FALSE;
3099
0
  double length = 0.0;
3100
0
  double seglength = 0.0;
3101
3102
  /* Return zero on non-sensical inputs */
3103
0
  if ( ! pa || pa->npoints < 2 )
3104
0
    return 0.0;
3105
3106
  /* See if we have a third dimension */
3107
0
  hasz = FLAGS_GET_Z(pa->flags);
3108
3109
  /* Initialize first point */
3110
0
  getPoint4d_p(pa, 0, &p);
3111
0
  geographic_point_init(p.x, p.y, &a);
3112
0
  if ( hasz )
3113
0
    za = p.z;
3114
3115
  /* Loop and sum the length for each segment */
3116
0
  for ( i = 1; i < pa->npoints; i++ )
3117
0
  {
3118
0
    seglength = 0.0;
3119
0
    getPoint4d_p(pa, i, &p);
3120
0
    geographic_point_init(p.x, p.y, &b);
3121
0
    if ( hasz )
3122
0
      zb = p.z;
3123
3124
    /* Special sphere case */
3125
0
    if ( s->a == s->b )
3126
0
      seglength = s->radius * sphere_distance(&a, &b);
3127
    /* Spheroid case */
3128
0
    else
3129
0
      seglength = spheroid_distance(&a, &b, s);
3130
3131
    /* Add in the vertical displacement if we're in 3D */
3132
0
    if ( hasz )
3133
0
      seglength = sqrt( (zb-za)*(zb-za) + seglength*seglength );
3134
3135
    /* Add this segment length to the total */
3136
0
    length += seglength;
3137
3138
    /* B gets incremented in the next loop, so we save the value here */
3139
0
    a = b;
3140
0
    za = zb;
3141
0
  }
3142
0
  return length;
3143
0
}
3144
3145
double lwgeom_length_spheroid(const LWGEOM *geom, const SPHEROID *s)
3146
0
{
3147
0
  int type;
3148
0
  uint32_t i = 0;
3149
0
  double length = 0.0;
3150
3151
0
  assert(geom);
3152
3153
  /* No length in nothing */
3154
0
  if ( lwgeom_is_empty(geom) )
3155
0
    return 0.0;
3156
3157
0
  type = geom->type;
3158
3159
0
  if ( type == POINTTYPE || type == MULTIPOINTTYPE ||
3160
0
       type == POLYGONTYPE || type == MULTIPOLYGONTYPE ||
3161
0
       type == TRIANGLETYPE || type == TINTYPE ||
3162
0
       type == POLYHEDRALSURFACETYPE || type == CURVEPOLYTYPE ||
3163
0
       type == MULTISURFACETYPE )
3164
0
    return 0.0;
3165
3166
0
  if ( type == LINETYPE )
3167
0
    return ptarray_length_spheroid(((LWLINE*)geom)->points, s);
3168
3169
0
  if ( lwtype_is_collection( type ) )
3170
0
  {
3171
0
    LWCOLLECTION *col = (LWCOLLECTION*)geom;
3172
3173
0
    for ( i = 0; i < col->ngeoms; i++ )
3174
0
    {
3175
0
      length += lwgeom_length_spheroid(col->geoms[i], s);
3176
0
    }
3177
0
    return length;
3178
0
  }
3179
3180
0
  lwerror("unsupported type passed to lwgeom_length_spheroid");
3181
0
  return 0.0;
3182
0
}
3183
3184
double lwgeom_perimeter_spheroid(const LWGEOM *geom, const SPHEROID *s)
3185
0
{
3186
0
  int type;
3187
0
  uint32_t i = 0;
3188
0
  double perimeter = 0.0;
3189
3190
0
  assert(geom);
3191
3192
  /* No perimeter in nothing */
3193
0
  if ( lwgeom_is_empty(geom) )
3194
0
    return 0.0;
3195
3196
0
  type = geom->type;
3197
3198
0
  if ( type == POLYGONTYPE )
3199
0
  {
3200
0
    LWPOLY *poly = (LWPOLY*)geom;
3201
0
    for ( i = 0; i < poly->nrings; i++ )
3202
0
    {
3203
0
      perimeter += ptarray_length_spheroid(poly->rings[i], s);
3204
0
    }
3205
0
    return perimeter;
3206
0
  }
3207
3208
0
  if ( type == TRIANGLETYPE )
3209
0
    return ptarray_length_spheroid(((LWTRIANGLE*)geom)->points, s);
3210
3211
0
  if ( lwtype_is_collection( type ) )
3212
0
  {
3213
0
    LWCOLLECTION *col = (LWCOLLECTION*)geom;
3214
3215
0
    for ( i = 0; i < col->ngeoms; i++ )
3216
0
    {
3217
0
      perimeter += lwgeom_perimeter_spheroid(col->geoms[i], s);
3218
0
    }
3219
0
    return perimeter;
3220
0
  }
3221
3222
0
  return 0.0;
3223
0
}
3224
3225
/**
3226
* When features are snapped or sometimes they are just this way, they are very close to
3227
* the geodetic bounds but slightly over. This routine nudges those points, and only
3228
* those points, back over to the bounds.
3229
* http://trac.osgeo.org/postgis/ticket/1292
3230
*/
3231
static int
3232
ptarray_nudge_geodetic(POINTARRAY *pa)
3233
0
{
3234
3235
0
  uint32_t i;
3236
0
  POINT4D p;
3237
0
  int altered = LW_FALSE;
3238
0
  int rv = LW_FALSE;
3239
0
  static double tolerance = 1e-10;
3240
3241
0
  if ( ! pa )
3242
0
    lwerror("ptarray_nudge_geodetic called with null input");
3243
3244
0
  for(i = 0; i < pa->npoints; i++ )
3245
0
  {
3246
0
    getPoint4d_p(pa, i, &p);
3247
0
    if ( p.x < -180.0 && (-180.0 - p.x <= tolerance) )
3248
0
    {
3249
0
      p.x = -180.0;
3250
0
      altered = LW_TRUE;
3251
0
    }
3252
0
    if ( p.x > 180.0 && (p.x - 180.0 <= tolerance) )
3253
0
    {
3254
0
      p.x = 180.0;
3255
0
      altered = LW_TRUE;
3256
0
    }
3257
0
    if ( p.y < -90.0 && (-90.0 - p.y <= tolerance) )
3258
0
    {
3259
0
      p.y = -90.0;
3260
0
      altered = LW_TRUE;
3261
0
    }
3262
0
    if ( p.y > 90.0 && (p.y - 90.0 <= tolerance) )
3263
0
    {
3264
0
      p.y = 90.0;
3265
0
      altered = LW_TRUE;
3266
0
    }
3267
0
    if ( altered == LW_TRUE )
3268
0
    {
3269
0
      ptarray_set_point4d(pa, i, &p);
3270
0
      altered = LW_FALSE;
3271
0
      rv = LW_TRUE;
3272
0
    }
3273
0
  }
3274
0
  return rv;
3275
0
}
3276
3277
/**
3278
* When features are snapped or sometimes they are just this way, they are very close to
3279
* the geodetic bounds but slightly over. This routine nudges those points, and only
3280
* those points, back over to the bounds.
3281
* http://trac.osgeo.org/postgis/ticket/1292
3282
*/
3283
int
3284
lwgeom_nudge_geodetic(LWGEOM *geom)
3285
0
{
3286
0
  int type;
3287
0
  uint32_t i = 0;
3288
0
  int rv = LW_FALSE;
3289
3290
0
  assert(geom);
3291
3292
  /* No points in nothing */
3293
0
  if ( lwgeom_is_empty(geom) )
3294
0
    return LW_FALSE;
3295
3296
0
  type = geom->type;
3297
3298
0
  if ( type == POINTTYPE )
3299
0
    return ptarray_nudge_geodetic(((LWPOINT*)geom)->point);
3300
3301
0
  if ( type == LINETYPE )
3302
0
    return ptarray_nudge_geodetic(((LWLINE*)geom)->points);
3303
3304
0
  if ( type == POLYGONTYPE )
3305
0
  {
3306
0
    LWPOLY *poly = (LWPOLY*)geom;
3307
0
    for ( i = 0; i < poly->nrings; i++ )
3308
0
    {
3309
0
      int n = ptarray_nudge_geodetic(poly->rings[i]);
3310
0
      rv = (rv == LW_TRUE ? rv : n);
3311
0
    }
3312
0
    return rv;
3313
0
  }
3314
3315
0
  if ( type == TRIANGLETYPE )
3316
0
    return ptarray_nudge_geodetic(((LWTRIANGLE*)geom)->points);
3317
3318
0
  if ( lwtype_is_collection( type ) )
3319
0
  {
3320
0
    LWCOLLECTION *col = (LWCOLLECTION*)geom;
3321
3322
0
    for ( i = 0; i < col->ngeoms; i++ )
3323
0
    {
3324
0
      int n = lwgeom_nudge_geodetic(col->geoms[i]);
3325
0
      rv = (rv == LW_TRUE ? rv : n);
3326
0
    }
3327
0
    return rv;
3328
0
  }
3329
3330
0
  lwerror("unsupported type (%s) passed to lwgeom_nudge_geodetic", lwtype_name(type));
3331
0
  return rv;
3332
0
}
3333
3334
3335
/**
3336
* Utility function for checking if P is within the cone defined by A1/A2.
3337
*/
3338
static int
3339
point_in_cone(const POINT3D *A1, const POINT3D *A2, const POINT3D *P)
3340
0
{
3341
0
  POINT3D AC; /* Center point of A1/A2 */
3342
0
  double min_similarity, similarity;
3343
3344
  /* Boundary case */
3345
0
  if (point3d_equals(A1, P) || point3d_equals(A2, P))
3346
0
    return LW_TRUE;
3347
3348
  /* The normalized sum bisects the angle between start and end. */
3349
0
  vector_sum(A1, A2, &AC);
3350
0
  normalize(&AC);
3351
3352
  /* The projection of start onto the center defines the minimum similarity */
3353
0
  min_similarity = dot_product(A1, &AC);
3354
3355
  /* If the edge is sufficiently curved, use the dot product test */
3356
0
  if (fabs(1.0 - min_similarity) > 1e-10)
3357
0
  {
3358
    /* The projection of candidate p onto the center */
3359
0
    similarity = dot_product(P, &AC);
3360
3361
    /* If the projection of the candidate is larger than */
3362
    /* the projection of the start point, the candidate */
3363
    /* must be closer to the center than the start, so */
3364
    /* therefore inside the cone */
3365
0
    if (similarity > min_similarity)
3366
0
    {
3367
0
      return LW_TRUE;
3368
0
    }
3369
0
    else
3370
0
    {
3371
0
      return LW_FALSE;
3372
0
    }
3373
0
  }
3374
0
  else
3375
0
  {
3376
    /* Where the edge is very narrow, the dot product test */
3377
    /* fails, but we can use the almost-planar nature of the */
3378
    /* problem space then to test if the vector from the */
3379
    /* candidate to the start point in a different direction */
3380
    /* to the vector from candidate to end point */
3381
    /* If so, then candidate is between start and end */
3382
0
    POINT3D PA1, PA2;
3383
0
    vector_difference(P, A1, &PA1);
3384
0
    vector_difference(P, A2, &PA2);
3385
0
    normalize(&PA1);
3386
0
    normalize(&PA2);
3387
0
    if (dot_product(&PA1, &PA2) < 0.0)
3388
0
    {
3389
0
      return LW_TRUE;
3390
0
    }
3391
0
    else
3392
0
    {
3393
0
      return LW_FALSE;
3394
0
    }
3395
0
  }
3396
0
  return LW_FALSE;
3397
0
}
3398
3399
3400
3401
/**
3402
* Utility function for edge_intersects(), signum with a tolerance
3403
* in determining if the value is zero.
3404
*/
3405
static int
3406
dot_product_side(const POINT3D *p, const POINT3D *q)
3407
0
{
3408
0
  double dp = dot_product(p, q);
3409
3410
0
  if ( FP_IS_ZERO(dp) )
3411
0
    return 0;
3412
3413
0
  return dp < 0.0 ? -1 : 1;
3414
0
}
3415
3416
/**
3417
* Returns non-zero if edges A and B interact. The type of interaction is given in the
3418
* return value with the bitmask elements defined above.
3419
*/
3420
uint32_t
3421
edge_intersects(const POINT3D *A1, const POINT3D *A2, const POINT3D *B1, const POINT3D *B2)
3422
0
{
3423
0
  POINT3D AN, BN, VN;  /* Normals to plane A and plane B */
3424
0
  double ab_dot;
3425
0
  int a1_side, a2_side, b1_side, b2_side;
3426
0
  int rv = PIR_NO_INTERACT;
3427
3428
  /* Normals to the A-plane and B-plane */
3429
0
  unit_normal(A1, A2, &AN);
3430
0
  unit_normal(B1, B2, &BN);
3431
3432
  /* Are A-plane and B-plane basically the same? */
3433
0
  ab_dot = dot_product(&AN, &BN);
3434
3435
  /*
3436
  * https://trac.osgeo.org/postgis/ticket/5765
3437
  * Failure because the colinearity check was
3438
  * triggering due to an overly loose equality
3439
  * check here.
3440
  * if ( FP_EQUALS(fabs(ab_dot), 1.0) )
3441
  */
3442
0
  if ( 1.0 - fabs(ab_dot) <= 10e-16 )
3443
0
  {
3444
    /* Co-linear case */
3445
0
    if ( point_in_cone(A1, A2, B1) || point_in_cone(A1, A2, B2) ||
3446
0
         point_in_cone(B1, B2, A1) || point_in_cone(B1, B2, A2) )
3447
0
    {
3448
0
      rv |= PIR_INTERSECTS;
3449
0
      rv |= PIR_COLINEAR;
3450
0
    }
3451
0
    return rv;
3452
0
  }
3453
3454
  /* What side of plane-A and plane-B do the end points */
3455
  /* of A and B fall? */
3456
0
  a1_side = dot_product_side(&BN, A1);
3457
0
  a2_side = dot_product_side(&BN, A2);
3458
0
  b1_side = dot_product_side(&AN, B1);
3459
0
  b2_side = dot_product_side(&AN, B2);
3460
3461
  /* Both ends of A on the same side of plane B. */
3462
0
  if ( a1_side == a2_side && a1_side != 0 )
3463
0
  {
3464
    /* No intersection. */
3465
0
    return PIR_NO_INTERACT;
3466
0
  }
3467
3468
  /* Both ends of B on the same side of plane A. */
3469
0
  if ( b1_side == b2_side && b1_side != 0 )
3470
0
  {
3471
    /* No intersection. */
3472
0
    return PIR_NO_INTERACT;
3473
0
  }
3474
3475
  /* A straddles B and B straddles A, so... */
3476
0
  if ( a1_side != a2_side && (a1_side + a2_side) == 0 &&
3477
0
       b1_side != b2_side && (b1_side + b2_side) == 0 )
3478
0
  {
3479
    /* Have to check if intersection point is inside both arcs */
3480
0
    unit_normal(&AN, &BN, &VN);
3481
0
    if ( point_in_cone(A1, A2, &VN) && point_in_cone(B1, B2, &VN) )
3482
0
    {
3483
0
      return PIR_INTERSECTS;
3484
0
    }
3485
3486
    /* Have to check if intersection point is inside both arcs */
3487
0
    vector_scale(&VN, -1);
3488
0
    if ( point_in_cone(A1, A2, &VN) && point_in_cone(B1, B2, &VN) )
3489
0
    {
3490
0
      return PIR_INTERSECTS;
3491
0
    }
3492
3493
0
    return PIR_NO_INTERACT;
3494
0
  }
3495
3496
  /* The rest are all intersects variants... */
3497
0
  rv |= PIR_INTERSECTS;
3498
3499
  /* A touches B */
3500
0
  if ( a1_side == 0 )
3501
0
  {
3502
    /* Touches at A1, A2 is on what side? */
3503
0
    rv |= (a2_side < 0 ? PIR_A_TOUCH_RIGHT : PIR_A_TOUCH_LEFT);
3504
0
  }
3505
0
  else if ( a2_side == 0 )
3506
0
  {
3507
    /* Touches at A2, A1 is on what side? */
3508
0
    rv |= (a1_side < 0 ? PIR_A_TOUCH_RIGHT : PIR_A_TOUCH_LEFT);
3509
0
  }
3510
3511
  /* B touches A */
3512
0
  if ( b1_side == 0 )
3513
0
  {
3514
    /* Touches at B1, B2 is on what side? */
3515
0
    rv |= (b2_side < 0 ? PIR_B_TOUCH_RIGHT : PIR_B_TOUCH_LEFT);
3516
0
  }
3517
0
  else if ( b2_side == 0 )
3518
0
  {
3519
    /* Touches at B2, B1 is on what side? */
3520
0
    rv |= (b1_side < 0 ? PIR_B_TOUCH_RIGHT : PIR_B_TOUCH_LEFT);
3521
0
  }
3522
3523
0
  return rv;
3524
0
}
3525
3526
/**
3527
* This routine returns LW_TRUE if the stabline joining the pt_outside and pt_to_test
3528
* crosses the ring an odd number of times, or if the pt_to_test is on the ring boundary itself,
3529
* returning LW_FALSE otherwise.
3530
* The pt_outside *must* be guaranteed to be outside the ring (use the geography_pt_outside() function
3531
* to derive one in postgis, or the gbox_pt_outside() function if you don't mind burning CPU cycles
3532
* building a gbox first).
3533
*/
3534
int ptarray_contains_point_sphere(const POINTARRAY *pa, const POINT2D *pt_outside, const POINT2D *pt_to_test)
3535
0
{
3536
0
  POINT3D S1, S2; /* Stab line end points */
3537
0
  POINT3D E1, E2; /* Edge end points (3-space) */
3538
0
  POINT2D p; /* Edge end points (lon/lat) */
3539
0
  uint32_t count = 0, i, inter;
3540
3541
  /* Null input, not enough points for a ring? You ain't closed! */
3542
0
  if ( ! pa || pa->npoints < 4 )
3543
0
    return LW_FALSE;
3544
3545
  /* Set up our stab line */
3546
0
  ll2cart(pt_to_test, &S1);
3547
0
  ll2cart(pt_outside, &S2);
3548
3549
  /* Initialize first point */
3550
0
  getPoint2d_p(pa, 0, &p);
3551
0
  ll2cart(&p, &E1);
3552
3553
  /* Walk every edge and see if the stab line hits it */
3554
0
  for ( i = 1; i < pa->npoints; i++ )
3555
0
  {
3556
0
    LWDEBUGF(4, "testing edge (%d)", i);
3557
0
    LWDEBUGF(4, "  start point == POINT(%.12g %.12g)", p.x, p.y);
3558
3559
    /* Read next point. */
3560
0
    getPoint2d_p(pa, i, &p);
3561
0
    ll2cart(&p, &E2);
3562
3563
    /* Skip over too-short edges. */
3564
0
    if ( point3d_equals(&E1, &E2) )
3565
0
    {
3566
0
      continue;
3567
0
    }
3568
3569
    /* Our test point is on an edge end! Point is "in ring" by our definition */
3570
0
    if ( point3d_equals(&S1, &E1) )
3571
0
    {
3572
0
      return LW_TRUE;
3573
0
    }
3574
3575
    /* Calculate relationship between stab line and edge */
3576
0
    inter = edge_intersects(&S1, &S2, &E1, &E2);
3577
3578
    /* We have some kind of interaction... */
3579
0
    if ( inter & PIR_INTERSECTS )
3580
0
    {
3581
      /* If the stabline is touching the edge, that implies the test point */
3582
      /* is on the edge, so we're done, the point is in (on) the ring. */
3583
0
      if ( (inter & PIR_A_TOUCH_RIGHT) || (inter & PIR_A_TOUCH_LEFT) )
3584
0
      {
3585
0
        return LW_TRUE;
3586
0
      }
3587
3588
      /* It's a touching interaction, disregard all the left-side ones. */
3589
      /* It's a co-linear intersection, ignore those. */
3590
0
      if ( inter & PIR_B_TOUCH_RIGHT || inter & PIR_COLINEAR )
3591
0
      {
3592
        /* Do nothing, to avoid double counts. */
3593
0
        LWDEBUGF(4,"    edge (%d) crossed, count == %d, disregarding to avoid double count", i, count);
3594
0
      }
3595
0
      else
3596
0
      {
3597
        /* Increment crossingn count. */
3598
0
        count++;
3599
0
        LWDEBUGF(4,"    edge (%d) crossed, count == %d", i, count);
3600
0
      }
3601
0
    }
3602
0
    else
3603
0
    {
3604
0
      LWDEBUGF(4,"    edge (%d) did not cross", i);
3605
0
    }
3606
3607
    /* Increment to next edge */
3608
0
    E1 = E2;
3609
0
  }
3610
3611
0
  LWDEBUGF(4,"final count == %d", count);
3612
3613
  /* An odd number of crossings implies containment! */
3614
0
  if ( count % 2 )
3615
0
  {
3616
0
    return LW_TRUE;
3617
0
  }
3618
3619
0
  return LW_FALSE;
3620
0
}
3621
3622
3623
/*
3624
* Given a geodetic bounding volume, calculate a lon/lat bounding
3625
* box that should contain the original feature that gave rise to
3626
* the geodetic box, in plate-carre space (planar lon/lat).
3627
*/
3628
int gbox_geocentric_get_gbox_cartesian(const GBOX *gbox_geocentric, GBOX *gbox_planar)
3629
0
{
3630
  /* Normalized corners of the bounding volume */
3631
0
  POINT3D corners[8];
3632
0
  POINT3D cap_center = {0,0,0};
3633
0
  double furthest_angle = 0.0;
3634
0
  double cap_angle = 0.0;
3635
0
  int all_longitudes = LW_FALSE;
3636
0
  double lon0 = -M_PI, lon1 = M_PI;
3637
0
  double lat0, lat1;
3638
0
  GEOGRAPHIC_POINT cap_center_g;
3639
3640
0
  if (!gbox_geocentric || !gbox_planar)
3641
0
  {
3642
0
    lwerror("Null pointer passed to %s", __func__);
3643
0
    return LW_FALSE;
3644
0
  }
3645
3646
0
#define CORNER_SET(ii, xx, yy, zz) { \
3647
0
  corners[ii].x = gbox_geocentric->xx; \
3648
0
  corners[ii].y = gbox_geocentric->yy; \
3649
0
  corners[ii].z = gbox_geocentric->zz; \
3650
0
  }
3651
3652
  /*
3653
  * First find a "centered" vector to serve as the mid-point
3654
  * of the input bounding volume.
3655
  */
3656
0
  CORNER_SET(0, xmin, ymin, zmin);
3657
0
  CORNER_SET(1, xmax, ymin, zmin);
3658
0
  CORNER_SET(2, xmin, ymax, zmin);
3659
0
  CORNER_SET(3, xmax, ymax, zmin);
3660
0
  CORNER_SET(4, xmin, ymin, zmax);
3661
0
  CORNER_SET(5, xmax, ymin, zmax);
3662
0
  CORNER_SET(6, xmin, ymax, zmax);
3663
0
  CORNER_SET(7, xmax, ymax, zmax);
3664
3665
  /*
3666
  * Normalize the volume corners
3667
  * and normalize the final vector.
3668
  */
3669
0
  for (uint32_t i = 0; i < 8; i++)
3670
0
  {
3671
0
    normalize(&(corners[i]));
3672
0
    cap_center.x += corners[i].x;
3673
0
    cap_center.y += corners[i].y;
3674
0
    cap_center.z += corners[i].z;
3675
0
  }
3676
0
  normalize(&cap_center);
3677
3678
  /*
3679
  * Find the volume corner that is furthest from the center,
3680
  * and calculate the angle between the center and the corner.
3681
  * Now we have a "cap" (center and angle)
3682
  */
3683
0
  for (uint32_t i = 0; i < 8; i++)
3684
0
  {
3685
0
    double angle = vector_angle(&cap_center, &(corners[i]));
3686
0
    if (angle > furthest_angle)
3687
0
      furthest_angle = angle;
3688
0
  }
3689
0
  cap_angle = furthest_angle;
3690
3691
  /*
3692
  * Calculate the planar box that contains the cap.
3693
  * If the cap contains a pole, then we include all longitudes
3694
  */
3695
0
  cart2geog(&cap_center, &cap_center_g);
3696
3697
  /* Check whether cap includes the south pole */
3698
0
  lat0 = cap_center_g.lat - cap_angle;
3699
0
  if (lat0 <= -M_PI_2)
3700
0
  {
3701
0
    lat0 = -M_PI_2;
3702
0
    all_longitudes = LW_TRUE;
3703
0
  }
3704
3705
  /* Check whether cap includes the north pole */
3706
0
  lat1 = cap_center_g.lat + cap_angle;
3707
0
  if (lat1 >= M_PI_2)
3708
0
  {
3709
0
    lat1 = M_PI_2;
3710
0
    all_longitudes = LW_TRUE;
3711
0
  }
3712
3713
0
  if (!all_longitudes)
3714
0
  {
3715
    // Compute the range of longitudes covered by the cap.  We use the law
3716
    // of sines for spherical triangles.  Consider the triangle ABC where
3717
    // A is the north pole, B is the center of the cap, and C is the point
3718
    // of tangency between the cap boundary and a line of longitude.  Then
3719
    // C is a right angle, and letting a,b,c denote the sides opposite A,B,C,
3720
    // we have sin(a)/sin(A) = sin(c)/sin(C), or sin(A) = sin(a)/sin(c).
3721
    // Here "a" is the cap angle, and "c" is the colatitude (90 degrees
3722
    // minus the latitude).  This formula also works for negative latitudes.
3723
    //
3724
    // The formula for sin(a) follows from the relationship h = 1 - cos(a).
3725
3726
0
    double sin_a = sin(cap_angle);
3727
0
    double sin_c = cos(cap_center_g.lat);
3728
0
    if (sin_a <= sin_c)
3729
0
    {
3730
0
      double angle_A = asin(sin_a / sin_c);
3731
0
      lon0 = remainder(cap_center_g.lon - angle_A, 2 * M_PI);
3732
0
      lon1 = remainder(cap_center_g.lon + angle_A, 2 * M_PI);
3733
0
    }
3734
0
    else
3735
0
    {
3736
0
      lon0 = -M_PI;
3737
0
      lon1 =  M_PI;
3738
0
    }
3739
0
  }
3740
3741
0
  gbox_planar->xmin = rad2deg(lon0);
3742
0
  gbox_planar->ymin = rad2deg(lat0);
3743
0
  gbox_planar->xmax = rad2deg(lon1);
3744
0
  gbox_planar->ymax = rad2deg(lat1);
3745
0
  FLAGS_SET_GEODETIC(gbox_planar->flags, 0);
3746
0
  FLAGS_SET_Z(gbox_planar->flags, 0);
3747
0
  FLAGS_SET_M(gbox_planar->flags, 0);
3748
3749
0
  return LW_TRUE;
3750
0
}
3751