Coverage Report

Created: 2026-08-14 06:58

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/postgis/liblwgeom/lwspheroid.c
Line
Count
Source
1
/**********************************************************************
2
 *
3
 * PostGIS - Spatial Types for PostgreSQL
4
 * http://postgis.net
5
 *
6
 * PostGIS is free software: you can redistribute it and/or modify
7
 * it under the terms of the GNU General Public License as published by
8
 * the Free Software Foundation, either version 2 of the License, or
9
 * (at your option) any later version.
10
 *
11
 * PostGIS is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
 * GNU General Public License for more details.
15
 *
16
 * You should have received a copy of the GNU General Public License
17
 * along with PostGIS.  If not, see <http://www.gnu.org/licenses/>.
18
 *
19
 **********************************************************************
20
 *
21
 * Copyright (C) 2009 Paul Ramsey <pramsey@cleverelephant.ca>
22
 * Copyright (C) 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
/* In proj4.9, GeographicLib is in special header */
32
#ifdef PROJ_GEODESIC
33
#include <geodesic.h>
34
#endif
35
36
/**
37
* Initialize spheroid object based on major and minor axis
38
*/
39
void spheroid_init(SPHEROID *s, double a, double b)
40
0
{
41
0
  s->a = a;
42
0
  s->b = b;
43
0
  s->f = (a - b) / a;
44
0
  s->e_sq = (a*a - b*b)/(a*a);
45
0
  s->radius = (2.0 * a + b ) / 3.0;
46
0
}
47
48
#ifndef PROJ_GEODESIC
49
static double spheroid_mu2(double alpha, const SPHEROID *s)
50
{
51
  double b2 = POW2(s->b);
52
  return POW2(cos(alpha)) * (POW2(s->a) - b2) / b2;
53
}
54
55
static double spheroid_big_a(double u2)
56
{
57
  return 1.0 + (u2 / 16384.0) * (4096.0 + u2 * (-768.0 + u2 * (320.0 - 175.0 * u2)));
58
}
59
60
static double spheroid_big_b(double u2)
61
{
62
  return (u2 / 1024.0) * (256.0 + u2 * (-128.0 + u2 * (74.0 - 47.0 * u2)));
63
}
64
#endif /* ! PROJ_GEODESIC */
65
66
67
#ifdef PROJ_GEODESIC
68
69
/**
70
* Computes the shortest distance along the surface of the spheroid
71
* between two points, using the inverse geodesic problem from
72
* GeographicLib (Karney 2013).
73
*
74
* @param a - location of first point
75
* @param b - location of second point
76
* @param s - spheroid to calculate on
77
* @return spheroidal distance between a and b in spheroid units
78
*/
79
double spheroid_distance(const GEOGRAPHIC_POINT *a, const GEOGRAPHIC_POINT *b, const SPHEROID *spheroid)
80
0
{
81
0
  struct geod_geodesic gd;
82
83
  /* Same point => zero distance */
84
0
  if ( geographic_point_equals(a, b) )
85
0
    return 0.0;
86
87
0
  geod_init(&gd, spheroid->a, spheroid->f);
88
0
  double lat1 = a->lat * 180.0 / M_PI;
89
0
  double lon1 = a->lon * 180.0 / M_PI;
90
0
  double lat2 = b->lat * 180.0 / M_PI;
91
0
  double lon2 = b->lon * 180.0 / M_PI;
92
0
  double s12 = 0.0; /* return distance */
93
0
  geod_inverse(&gd, lat1, lon1, lat2, lon2, &s12, 0, 0);
94
0
  return s12;
95
0
}
96
97
/**
98
* Computes the forward azimuth of the geodesic joining two points on
99
* the spheroid, using the inverse geodesic problem (Karney 2013).
100
*
101
* @param r - location of first point
102
* @param s - location of second point
103
* @return azimuth of line joining r to s (but not reverse)
104
*/
105
double spheroid_direction(const GEOGRAPHIC_POINT *a, const GEOGRAPHIC_POINT *b, const SPHEROID *spheroid)
106
0
{
107
0
  struct geod_geodesic gd;
108
0
  geod_init(&gd, spheroid->a, spheroid->f);
109
0
  double lat1 = a->lat * 180.0 / M_PI;
110
0
  double lon1 = a->lon * 180.0 / M_PI;
111
0
  double lat2 = b->lat * 180.0 / M_PI;
112
0
  double lon2 = b->lon * 180.0 / M_PI;
113
0
  double azi1; /* return azimuth */
114
0
  geod_inverse(&gd, lat1, lon1, lat2, lon2, 0, &azi1, 0);
115
0
  return azi1 * M_PI / 180.0;
116
0
}
117
118
/**
119
* Given a location, an azimuth and a distance, computes the location of
120
* the projected point. Using the direct geodesic problem from
121
* GeographicLib (Karney 2013).
122
*
123
* @param r - location of first point
124
* @param distance - distance in meters
125
* @param azimuth - azimuth in radians
126
* @return g - location of projected point
127
*/
128
int spheroid_project(const GEOGRAPHIC_POINT *r, const SPHEROID *spheroid, double distance, double azimuth, GEOGRAPHIC_POINT *g)
129
0
{
130
0
  struct geod_geodesic gd;
131
0
  geod_init(&gd, spheroid->a, spheroid->f);
132
0
  double lat1 = r->lat * 180.0 / M_PI;
133
0
  double lon1 = r->lon * 180.0 / M_PI;
134
0
  double lat2, lon2; /* return projected position */
135
0
  geod_direct(&gd, lat1, lon1, azimuth * 180.0 / M_PI, distance, &lat2, &lon2, 0);
136
0
  g->lat = lat2 * M_PI / 180.0;
137
0
  g->lon = lon2 * M_PI / 180.0;
138
0
  return LW_SUCCESS;
139
0
}
140
141
142
static double ptarray_area_spheroid(const POINTARRAY *pa, const SPHEROID *spheroid)
143
0
{
144
  /* Return zero on non-sensical inputs */
145
0
  if ( ! pa || pa->npoints < 4 )
146
0
    return 0.0;
147
148
0
  struct geod_geodesic gd;
149
0
  geod_init(&gd, spheroid->a, spheroid->f);
150
0
  struct geod_polygon poly;
151
0
  geod_polygon_init(&poly, 0);
152
0
  uint32_t i;
153
0
  double area; /* returned polygon area */
154
0
  POINT2D p; /* long/lat units are degrees */
155
156
  /* Pass points from point array; don't close the linearring */
157
0
  for ( i = 0; i < pa->npoints - 1; i++ )
158
0
  {
159
0
    getPoint2d_p(pa, i, &p);
160
0
    geod_polygon_addpoint(&gd, &poly, p.y, p.x);
161
0
    LWDEBUGF(4, "geod_polygon_addpoint %d: %.12g %.12g", i, p.y, p.x);
162
0
  }
163
0
  i = geod_polygon_compute(&gd, &poly, 0, 1, &area, 0);
164
0
  if ( i != pa->npoints - 1 )
165
0
  {
166
0
    lwerror("ptarray_area_spheroid: different number of points %d vs %d",
167
0
        i, pa->npoints - 1);
168
0
  }
169
0
  LWDEBUGF(4, "geod_polygon_compute area: %.12g", area);
170
0
  return fabs(area);
171
0
}
172
173
/* Above use Proj GeographicLib */
174
#else /* ! PROJ_GEODESIC */
175
/* Below use pre-version 2.2 geodesic functions */
176
177
/**
178
* Computes the shortest distance along the surface of the spheroid
179
* between two points. Based on Vincenty's formula for the geodetic
180
* inverse problem as described in "Geocentric Datum of Australia
181
* Technical Manual", Chapter 4. Tested against:
182
* http://mascot.gdbc.gov.bc.ca/mascot/util1a.html
183
* and
184
* http://www.ga.gov.au/nmd/geodesy/datums/vincenty_inverse.jsp
185
*
186
* @param a - location of first point.
187
* @param b - location of second point.
188
* @param s - spheroid to calculate on
189
* @return spheroidal distance between a and b in spheroid units.
190
*/
191
double spheroid_distance(const GEOGRAPHIC_POINT *a, const GEOGRAPHIC_POINT *b, const SPHEROID *spheroid)
192
{
193
  double lambda = (b->lon - a->lon);
194
  double f = spheroid->f;
195
  double omf = 1 - spheroid->f;
196
  double u1, u2;
197
  double cos_u1, cos_u2;
198
  double sin_u1, sin_u2;
199
  double big_a, big_b, delta_sigma;
200
  double alpha, sin_alpha, cos_alphasq, c;
201
  double sigma, sin_sigma, cos_sigma, cos2_sigma_m, sqrsin_sigma, last_lambda, omega;
202
  double cos_lambda, sin_lambda;
203
  double distance;
204
  int i = 0;
205
206
  /* Same point => zero distance */
207
  if ( geographic_point_equals(a, b) )
208
  {
209
    return 0.0;
210
  }
211
212
  u1 = atan(omf * tan(a->lat));
213
  cos_u1 = cos(u1);
214
  sin_u1 = sin(u1);
215
  u2 = atan(omf * tan(b->lat));
216
  cos_u2 = cos(u2);
217
  sin_u2 = sin(u2);
218
219
  omega = lambda;
220
  do
221
  {
222
    cos_lambda = cos(lambda);
223
    sin_lambda = sin(lambda);
224
    sqrsin_sigma = POW2(cos_u2 * sin_lambda) +
225
                   POW2((cos_u1 * sin_u2 - sin_u1 * cos_u2 * cos_lambda));
226
    sin_sigma = sqrt(sqrsin_sigma);
227
    cos_sigma = sin_u1 * sin_u2 + cos_u1 * cos_u2 * cos_lambda;
228
    sigma = atan2(sin_sigma, cos_sigma);
229
    sin_alpha = cos_u1 * cos_u2 * sin_lambda / sin(sigma);
230
231
    /* Numerical stability issue, ensure asin is not NaN */
232
    if ( sin_alpha > 1.0 )
233
      alpha = M_PI_2;
234
    else if ( sin_alpha < -1.0 )
235
      alpha = -1.0 * M_PI_2;
236
    else
237
      alpha = asin(sin_alpha);
238
239
    cos_alphasq = POW2(cos(alpha));
240
    cos2_sigma_m = cos(sigma) - (2.0 * sin_u1 * sin_u2 / cos_alphasq);
241
242
    /* Numerical stability issue, cos2 is in range */
243
    if ( cos2_sigma_m > 1.0 )
244
      cos2_sigma_m = 1.0;
245
    if ( cos2_sigma_m < -1.0 )
246
      cos2_sigma_m = -1.0;
247
248
    c = (f / 16.0) * cos_alphasq * (4.0 + f * (4.0 - 3.0 * cos_alphasq));
249
    last_lambda = lambda;
250
    lambda = omega + (1.0 - c) * f * sin(alpha) * (sigma + c * sin(sigma) *
251
             (cos2_sigma_m + c * cos(sigma) * (-1.0 + 2.0 * POW2(cos2_sigma_m))));
252
    i++;
253
  }
254
  while ( (i < 999) && (lambda != 0.0) && (fabs((last_lambda - lambda)/lambda) > 1.0e-9) );
255
256
  u2 = spheroid_mu2(alpha, spheroid);
257
  big_a = spheroid_big_a(u2);
258
  big_b = spheroid_big_b(u2);
259
  delta_sigma = big_b * sin_sigma * (cos2_sigma_m + (big_b / 4.0) * (cos_sigma * (-1.0 + 2.0 * POW2(cos2_sigma_m)) -
260
                                     (big_b / 6.0) * cos2_sigma_m * (-3.0 + 4.0 * sqrsin_sigma) * (-3.0 + 4.0 * POW2(cos2_sigma_m))));
261
262
  distance = spheroid->b * big_a * (sigma - delta_sigma);
263
264
  /* Algorithm failure, distance == NaN, fallback to sphere */
265
  if ( distance != distance )
266
  {
267
    lwerror("spheroid_distance returned NaN: (%.20g %.20g) (%.20g %.20g) a = %.20g b = %.20g",a->lat, a->lon, b->lat, b->lon, spheroid->a, spheroid->b);
268
    return spheroid->radius * sphere_distance(a, b);
269
  }
270
271
  return distance;
272
}
273
274
/**
275
* Computes the direction of the geodesic joining two points on
276
* the spheroid. Based on Vincenty's formula for the geodetic
277
* inverse problem as described in "Geocentric Datum of Australia
278
* Technical Manual", Chapter 4. Tested against:
279
* http://mascot.gdbc.gov.bc.ca/mascot/util1a.html
280
* and
281
* http://www.ga.gov.au/nmd/geodesy/datums/vincenty_inverse.jsp
282
*
283
* @param r - location of first point
284
* @param s - location of second point
285
* @return azimuth of line joining r and s
286
*/
287
double spheroid_direction(const GEOGRAPHIC_POINT *r, const GEOGRAPHIC_POINT *s, const SPHEROID *spheroid)
288
{
289
  int i = 0;
290
  double lambda = s->lon - r->lon;
291
  double omf = 1 - spheroid->f;
292
  double u1 = atan(omf * tan(r->lat));
293
  double cos_u1 = cos(u1);
294
  double sin_u1 = sin(u1);
295
  double u2 = atan(omf * tan(s->lat));
296
  double cos_u2 = cos(u2);
297
  double sin_u2 = sin(u2);
298
299
  double omega = lambda;
300
  double alpha, sigma, sin_sigma, cos_sigma, cos2_sigma_m, sqr_sin_sigma, last_lambda;
301
  double sin_alpha, cos_alphasq, C, alphaFD;
302
  do
303
  {
304
    sqr_sin_sigma = POW2(cos_u2 * sin(lambda)) +
305
                    POW2((cos_u1 * sin_u2 - sin_u1 * cos_u2 * cos(lambda)));
306
    sin_sigma = sqrt(sqr_sin_sigma);
307
    cos_sigma = sin_u1 * sin_u2 + cos_u1 * cos_u2 * cos(lambda);
308
    sigma = atan2(sin_sigma, cos_sigma);
309
    sin_alpha = cos_u1 * cos_u2 * sin(lambda) / sin(sigma);
310
311
    /* Numerical stability issue, ensure asin is not NaN */
312
    if ( sin_alpha > 1.0 )
313
      alpha = M_PI_2;
314
    else if ( sin_alpha < -1.0 )
315
      alpha = -1.0 * M_PI_2;
316
    else
317
      alpha = asin(sin_alpha);
318
319
    cos_alphasq = POW2(cos(alpha));
320
    cos2_sigma_m = cos(sigma) - (2.0 * sin_u1 * sin_u2 / cos_alphasq);
321
322
    /* Numerical stability issue, cos2 is in range */
323
    if ( cos2_sigma_m > 1.0 )
324
      cos2_sigma_m = 1.0;
325
    if ( cos2_sigma_m < -1.0 )
326
      cos2_sigma_m = -1.0;
327
328
    C = (spheroid->f / 16.0) * cos_alphasq * (4.0 + spheroid->f * (4.0 - 3.0 * cos_alphasq));
329
    last_lambda = lambda;
330
    lambda = omega + (1.0 - C) * spheroid->f * sin(alpha) * (sigma + C * sin(sigma) *
331
             (cos2_sigma_m + C * cos(sigma) * (-1.0 + 2.0 * POW2(cos2_sigma_m))));
332
    i++;
333
  }
334
  while ( (i < 999) && (lambda != 0) && (fabs((last_lambda - lambda) / lambda) > 1.0e-9) );
335
336
  alphaFD = atan2((cos_u2 * sin(lambda)),
337
                  (cos_u1 * sin_u2 - sin_u1 * cos_u2 * cos(lambda)));
338
  if (alphaFD < 0.0)
339
  {
340
    alphaFD = alphaFD + 2.0 * M_PI;
341
  }
342
  if (alphaFD > 2.0 * M_PI)
343
  {
344
    alphaFD = alphaFD - 2.0 * M_PI;
345
  }
346
  return alphaFD;
347
}
348
349
350
/**
351
* Given a location, an azimuth and a distance, computes the
352
* location of the projected point. Based on Vincenty's formula
353
* for the geodetic direct problem as described in "Geocentric
354
* Datum of Australia Technical Manual", Chapter 4. Tested against:
355
* http://mascot.gdbc.gov.bc.ca/mascot/util1b.html
356
* and
357
* http://www.ga.gov.au/nmd/geodesy/datums/vincenty_direct.jsp
358
*
359
* @param r - location of first point.
360
* @param distance - distance in meters.
361
* @param azimuth - azimuth in radians.
362
* @return s - location of projected point.
363
*/
364
int spheroid_project(const GEOGRAPHIC_POINT *r, const SPHEROID *spheroid, double distance, double azimuth, GEOGRAPHIC_POINT *g)
365
{
366
  double omf = 1 - spheroid->f;
367
  double tan_u1 = omf * tan(r->lat);
368
  double u1 = atan(tan_u1);
369
  double sigma, last_sigma, delta_sigma, two_sigma_m;
370
  double sigma1, sin_alpha, alpha, cos_alphasq;
371
  double u2, A, B;
372
  double lat2, lambda, lambda2, C, omega;
373
  int i = 0;
374
375
  if (azimuth < 0.0)
376
  {
377
    azimuth = azimuth + M_PI * 2.0;
378
  }
379
  if (azimuth > (M_PI * 2.0))
380
  {
381
    azimuth = azimuth - M_PI * 2.0;
382
  }
383
384
  sigma1 = atan2(tan_u1, cos(azimuth));
385
  sin_alpha = cos(u1) * sin(azimuth);
386
  alpha = asin(sin_alpha);
387
  cos_alphasq = 1.0 - POW2(sin_alpha);
388
389
  u2 = spheroid_mu2(alpha, spheroid);
390
  A = spheroid_big_a(u2);
391
  B = spheroid_big_b(u2);
392
393
  sigma = (distance / (spheroid->b * A));
394
  do
395
  {
396
    two_sigma_m = 2.0 * sigma1 + sigma;
397
    delta_sigma = B * sin(sigma) * (cos(two_sigma_m) + (B / 4.0) * (cos(sigma) * (-1.0 + 2.0 * POW2(cos(two_sigma_m)) - (B / 6.0) * cos(two_sigma_m) * (-3.0 + 4.0 * POW2(sin(sigma))) * (-3.0 + 4.0 * POW2(cos(two_sigma_m))))));
398
    last_sigma = sigma;
399
    sigma = (distance / (spheroid->b * A)) + delta_sigma;
400
    i++;
401
  }
402
  while (i < 999 && fabs((last_sigma - sigma) / sigma) > 1.0e-9);
403
404
  lat2 = atan2((sin(u1) * cos(sigma) + cos(u1) * sin(sigma) *
405
                cos(azimuth)), (omf * sqrt(POW2(sin_alpha) +
406
                                           POW2(sin(u1) * sin(sigma) - cos(u1) * cos(sigma) *
407
                                                cos(azimuth)))));
408
  lambda = atan2((sin(sigma) * sin(azimuth)), (cos(u1) * cos(sigma) -
409
                 sin(u1) * sin(sigma) * cos(azimuth)));
410
  C = (spheroid->f / 16.0) * cos_alphasq * (4.0 + spheroid->f * (4.0 - 3.0 * cos_alphasq));
411
  omega = lambda - (1.0 - C) * spheroid->f * sin_alpha * (sigma + C * sin(sigma) *
412
          (cos(two_sigma_m) + C * cos(sigma) * (-1.0 + 2.0 * POW2(cos(two_sigma_m)))));
413
  lambda2 = r->lon + omega;
414
  g->lat = lat2;
415
  g->lon = lambda2;
416
  return LW_SUCCESS;
417
}
418
419
420
static inline double spheroid_prime_vertical_radius_of_curvature(double latitude, const SPHEROID *spheroid)
421
{
422
  return spheroid->a / (sqrt(1.0 - spheroid->e_sq * POW2(sin(latitude))));
423
}
424
425
static inline double spheroid_parallel_arc_length(double latitude, double deltaLongitude, const SPHEROID *spheroid)
426
{
427
  return spheroid_prime_vertical_radius_of_curvature(latitude, spheroid)
428
         * cos(latitude)
429
         * deltaLongitude;
430
}
431
432
433
/**
434
* Computes the area on the spheroid of a box bounded by meridians and
435
* parallels. The box is defined by two points, the South West corner
436
* and the North East corner. Formula based on Bagratuni 1967.
437
*
438
* @param southWestCorner - lower left corner of bounding box.
439
* @param northEastCorner - upper right corner of bounding box.
440
* @return area in square meters.
441
*/
442
static double spheroid_boundingbox_area(const GEOGRAPHIC_POINT *southWestCorner, const GEOGRAPHIC_POINT *northEastCorner, const SPHEROID *spheroid)
443
{
444
  double z0 = (northEastCorner->lon - southWestCorner->lon) * POW2(spheroid->b) / 2.0;
445
  double e = sqrt(spheroid->e_sq);
446
  double sinPhi1 = sin(southWestCorner->lat);
447
  double sinPhi2 = sin(northEastCorner->lat);
448
  double t1p1 = sinPhi1 / (1.0 - spheroid->e_sq * sinPhi1 * sinPhi1);
449
  double t1p2 = sinPhi2 / (1.0 - spheroid->e_sq * sinPhi2 * sinPhi2);
450
  double oneOver2e = 1.0 / (2.0 * e);
451
  double t2p1 = oneOver2e * log((1.0 + e * sinPhi1) / (1.0 - e * sinPhi1));
452
  double t2p2 = oneOver2e * log((1.0 + e * sinPhi2) / (1.0 - e * sinPhi2));
453
  return z0 * (t1p2 + t2p2) - z0 * (t1p1 + t2p1);
454
}
455
456
/**
457
* This function doesn't work for edges crossing the dateline or in the southern
458
* hemisphere. Points are pre-conditioned in ptarray_area_spheroid.
459
*/
460
static double spheroid_striparea(const GEOGRAPHIC_POINT *a, const GEOGRAPHIC_POINT *b, double latitude_min, const SPHEROID *spheroid)
461
{
462
  GEOGRAPHIC_POINT A, B, mL, nR;
463
  double deltaLng, baseArea, topArea;
464
  double bE, tE, ratio, sign;
465
466
  A = *a;
467
  B = *b;
468
469
  mL.lat = latitude_min;
470
  mL.lon = FP_MIN(A.lon, B.lon);
471
  nR.lat = FP_MIN(A.lat, B.lat);
472
  nR.lon = FP_MAX(A.lon, B.lon);
473
  LWDEBUGF(4, "mL (%.12g %.12g)", mL.lat, mL.lon);
474
  LWDEBUGF(4, "nR (%.12g %.12g)", nR.lat, nR.lon);
475
  baseArea = spheroid_boundingbox_area(&mL, &nR, spheroid);
476
  LWDEBUGF(4, "baseArea %.12g", baseArea);
477
478
  mL.lat = FP_MIN(A.lat, B.lat);
479
  mL.lon = FP_MIN(A.lon, B.lon);
480
  nR.lat = FP_MAX(A.lat, B.lat);
481
  nR.lon = FP_MAX(A.lon, B.lon);
482
  LWDEBUGF(4, "mL (%.12g %.12g)", mL.lat, mL.lon);
483
  LWDEBUGF(4, "nR (%.12g %.12g)", nR.lat, nR.lon);
484
  topArea = spheroid_boundingbox_area(&mL, &nR, spheroid);
485
  LWDEBUGF(4, "topArea %.12g", topArea);
486
487
  deltaLng = B.lon - A.lon;
488
  LWDEBUGF(4, "deltaLng %.12g", deltaLng);
489
  bE = spheroid_parallel_arc_length(A.lat, deltaLng, spheroid);
490
  tE = spheroid_parallel_arc_length(B.lat, deltaLng, spheroid);
491
  LWDEBUGF(4, "bE %.12g", bE);
492
  LWDEBUGF(4, "tE %.12g", tE);
493
494
  ratio = (bE + tE)/tE;
495
  sign = SIGNUM(B.lon - A.lon);
496
  return (baseArea + topArea / ratio) * sign;
497
}
498
499
static double ptarray_area_spheroid(const POINTARRAY *pa, const SPHEROID *spheroid)
500
{
501
  GEOGRAPHIC_POINT a, b;
502
  POINT2D p;
503
  uint32_t i;
504
  double area = 0.0;
505
  GBOX gbox2d;
506
  int in_south = LW_FALSE;
507
  double delta_lon_tolerance;
508
  double latitude_min;
509
510
  gbox2d.flags = lwflags(0, 0, 0);
511
512
  /* Return zero on non-sensical inputs */
513
  if ( ! pa || pa->npoints < 4 )
514
    return 0.0;
515
516
  /* Get the raw min/max values for the latitudes */
517
  ptarray_calculate_gbox_cartesian(pa, &gbox2d);
518
519
  if ( SIGNUM(gbox2d.ymin) != SIGNUM(gbox2d.ymax) )
520
    lwerror("ptarray_area_spheroid: cannot handle ptarray that crosses equator");
521
522
  /* Geodetic bbox < 0.0 implies geometry is entirely in southern hemisphere */
523
  if ( gbox2d.ymax < 0.0 )
524
    in_south = LW_TRUE;
525
526
  LWDEBUGF(4, "gbox2d.ymax %.12g", gbox2d.ymax);
527
528
  /* Tolerance for strip area calculation */
529
  if ( in_south )
530
  {
531
    delta_lon_tolerance = (90.0 / (fabs(gbox2d.ymin) / 8.0) - 2.0) / 10000.0;
532
    latitude_min = deg2rad(fabs(gbox2d.ymax));
533
  }
534
  else
535
  {
536
    delta_lon_tolerance = (90.0 / (fabs(gbox2d.ymax) / 8.0) - 2.0) / 10000.0;
537
    latitude_min = deg2rad(gbox2d.ymin);
538
  }
539
540
  /* Initialize first point */
541
  getPoint2d_p(pa, 0, &p);
542
  geographic_point_init(p.x, p.y, &a);
543
544
  for ( i = 1; i < pa->npoints; i++ )
545
  {
546
    GEOGRAPHIC_POINT a1, b1;
547
    double strip_area = 0.0;
548
    double delta_lon = 0.0;
549
    LWDEBUGF(4, "edge #%d", i);
550
551
    getPoint2d_p(pa, i, &p);
552
    geographic_point_init(p.x, p.y, &b);
553
554
    a1 = a;
555
    b1 = b;
556
557
    /* Flip into north if in south */
558
    if ( in_south )
559
    {
560
      a1.lat = -1.0 * a1.lat;
561
      b1.lat = -1.0 * b1.lat;
562
    }
563
564
    LWDEBUGF(4, "in_south %d", in_south);
565
566
    LWDEBUGF(4, "crosses_dateline(a, b) %d", crosses_dateline(&a, &b) );
567
568
    if ( crosses_dateline(&a, &b) )
569
    {
570
      double shift;
571
572
      if ( a1.lon > 0.0 )
573
        shift = (M_PI - a1.lon) + 0.088; /* About 5deg more */
574
      else
575
        shift = (M_PI - b1.lon) + 0.088; /* About 5deg more */
576
577
      LWDEBUGF(4, "shift: %.8g", shift);
578
      LWDEBUGF(4, "before shift a1(%.8g %.8g) b1(%.8g %.8g)", a1.lat, a1.lon, b1.lat, b1.lon);
579
      point_shift(&a1, shift);
580
      point_shift(&b1, shift);
581
      LWDEBUGF(4, "after shift a1(%.8g %.8g) b1(%.8g %.8g)", a1.lat, a1.lon, b1.lat, b1.lon);
582
583
    }
584
585
586
    delta_lon = fabs(b1.lon - a1.lon);
587
588
    LWDEBUGF(4, "a1(%.18g %.18g) b1(%.18g %.18g)", a1.lat, a1.lon, b1.lat, b1.lon);
589
    LWDEBUGF(4, "delta_lon %.18g", delta_lon);
590
    LWDEBUGF(4, "delta_lon_tolerance %.18g", delta_lon_tolerance);
591
592
    if ( delta_lon > 0.0 )
593
    {
594
      if ( delta_lon < delta_lon_tolerance )
595
      {
596
        strip_area = spheroid_striparea(&a1, &b1, latitude_min, spheroid);
597
        LWDEBUGF(4, "strip_area %.12g", strip_area);
598
        area += strip_area;
599
      }
600
      else
601
      {
602
        GEOGRAPHIC_POINT p, q;
603
        double step = floor(delta_lon / delta_lon_tolerance);
604
        double distance = spheroid_distance(&a1, &b1, spheroid);
605
        double pDistance = 0.0;
606
        int j = 0;
607
        LWDEBUGF(4, "step %.18g", step);
608
        LWDEBUGF(4, "distance %.18g", distance);
609
        step = distance / step;
610
        LWDEBUGF(4, "step %.18g", step);
611
        p = a1;
612
        while (pDistance < (distance - step * 1.01))
613
        {
614
          double azimuth = spheroid_direction(&p, &b1, spheroid);
615
          j++;
616
          LWDEBUGF(4, "  iteration %d", j);
617
          LWDEBUGF(4, "  azimuth %.12g", azimuth);
618
          pDistance = pDistance + step;
619
          LWDEBUGF(4, "  pDistance %.12g", pDistance);
620
          spheroid_project(&p, spheroid, step, azimuth, &q);
621
          strip_area = spheroid_striparea(&p, &q, latitude_min, spheroid);
622
          LWDEBUGF(4, "  strip_area %.12g", strip_area);
623
          area += strip_area;
624
          LWDEBUGF(4, "  area %.12g", area);
625
          p.lat = q.lat;
626
          p.lon = q.lon;
627
        }
628
        strip_area = spheroid_striparea(&p, &b1, latitude_min, spheroid);
629
        area += strip_area;
630
      }
631
    }
632
633
    /* B gets incremented in the next loop, so we save the value here */
634
    a = b;
635
  }
636
  return fabs(area);
637
}
638
#endif /* else ! PROJ_GEODESIC */
639
640
/**
641
* Calculate the area of an LWGEOM. Anything except POLYGON, MULTIPOLYGON
642
* and GEOMETRYCOLLECTION return zero immediately. Multi's recurse, polygons
643
* calculate external ring area and subtract internal ring area. A GBOX is
644
* required to check relationship to equator an outside point.
645
* WARNING: Does NOT WORK for polygons over equator or pole.
646
*/
647
double lwgeom_area_spheroid(const LWGEOM *lwgeom, const SPHEROID *spheroid)
648
0
{
649
0
  int type;
650
651
0
  assert(lwgeom);
652
653
  /* No area in nothing */
654
0
  if ( lwgeom_is_empty(lwgeom) )
655
0
    return 0.0;
656
657
  /* Read the geometry type number */
658
0
  type = lwgeom->type;
659
660
  /* Anything but polygons and collections returns zero */
661
0
  if ( ! ( type == POLYGONTYPE || type == MULTIPOLYGONTYPE || type == COLLECTIONTYPE ) )
662
0
    return 0.0;
663
664
  /* Actually calculate area */
665
0
  if ( type == POLYGONTYPE )
666
0
  {
667
0
    LWPOLY *poly = (LWPOLY*)lwgeom;
668
0
    uint32_t i;
669
0
    double area = 0.0;
670
671
    /* Just in case there's no rings */
672
0
    if ( poly->nrings < 1 )
673
0
      return 0.0;
674
675
    /* First, the area of the outer ring */
676
0
    area += ptarray_area_spheroid(poly->rings[0], spheroid);
677
678
    /* Subtract areas of inner rings */
679
0
    for ( i = 1; i < poly->nrings; i++ )
680
0
    {
681
0
      area -=  ptarray_area_spheroid(poly->rings[i], spheroid);
682
0
    }
683
0
    return area;
684
0
  }
685
686
  /* Recurse into sub-geometries to get area */
687
0
  if ( type == MULTIPOLYGONTYPE || type == COLLECTIONTYPE )
688
0
  {
689
0
    LWCOLLECTION *col = (LWCOLLECTION*)lwgeom;
690
0
    uint32_t i;
691
0
    double area = 0.0;
692
693
0
    for ( i = 0; i < col->ngeoms; i++ )
694
0
    {
695
0
      area += lwgeom_area_spheroid(col->geoms[i], spheroid);
696
0
    }
697
0
    return area;
698
0
  }
699
700
  /* Shouldn't get here. */
701
0
  return 0.0;
702
0
}
703
704
705