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