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