1 // Boost.Geometry
2
3 // Copyright (c) 2018 Adeel Ahmad, Islamabad, Pakistan.
4
5 // Contributed and/or modified by Adeel Ahmad, as part of Google Summer of Code 2018 program.
6
7 // This file was modified by Oracle on 2019.
8 // Modifications copyright (c) 2019 Oracle and/or its affiliates.
9
10 // Contributed and/or modified by Adam Wulkiewicz, on behalf of Oracle
11
12 // Use, modification and distribution is subject to the Boost Software License,
13 // Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at
14 // http://www.boost.org/LICENSE_1_0.txt)
15
16 // This file is converted from GeographicLib, https://geographiclib.sourceforge.io
17 // GeographicLib is originally written by Charles Karney.
18
19 // Author: Charles Karney (2008-2017)
20
21 // Last updated version of GeographicLib: 1.49
22
23 // Original copyright notice:
24
25 // Copyright (c) Charles Karney (2008-2017) <charles@karney.com> and licensed
26 // under the MIT/X11 License. For more information, see
27 // https://geographiclib.sourceforge.io
28
29 #ifndef BOOST_GEOMETRY_FORMULAS_KARNEY_INVERSE_HPP
30 #define BOOST_GEOMETRY_FORMULAS_KARNEY_INVERSE_HPP
31
32
33 #include <boost/math/constants/constants.hpp>
34 #include <boost/math/special_functions/hypot.hpp>
35
36 #include <boost/geometry/util/condition.hpp>
37 #include <boost/geometry/util/math.hpp>
38 #include <boost/geometry/util/series_expansion.hpp>
39 #include <boost/geometry/util/normalize_spheroidal_coordinates.hpp>
40
41 #include <boost/geometry/formulas/flattening.hpp>
42 #include <boost/geometry/formulas/result_inverse.hpp>
43
44
45 namespace boost { namespace geometry { namespace math {
46
47 // TODO: Moved temporarily because of C++11 is used
48
49 /*!
50 \brief The exact difference of two angles reduced to (-180deg, 180deg].
51 */
52 template<typename T>
difference_angle(T const & x,T const & y,T & e)53 inline T difference_angle(T const& x, T const& y, T& e)
54 {
55 T t, d = math::sum_error(std::remainder(-x, T(360)), std::remainder(y, T(360)), t);
56
57 normalize_azimuth<degree, T>(d);
58
59 // Here y - x = d + t (mod 360), exactly, where d is in (-180,180] and
60 // abs(t) <= eps (eps = 2^-45 for doubles). The only case where the
61 // addition of t takes the result outside the range (-180,180] is d = 180
62 // and t > 0. The case, d = -180 + eps, t = -eps, can't happen, since
63 // sum_error would have returned the exact result in such a case (i.e., given t = 0).
64 return math::sum_error(d == 180 && t > 0 ? -180 : d, t, e);
65 }
66
67 }}} // namespace boost::geometry::math
68
69
70 namespace boost { namespace geometry { namespace formula
71 {
72
73 namespace se = series_expansion;
74
75 /*!
76 \brief The solution of the inverse problem of geodesics on latlong coordinates,
77 after Karney (2011).
78 \author See
79 - Charles F.F Karney, Algorithms for geodesics, 2011
80 https://arxiv.org/pdf/1109.4448.pdf
81 */
82 template <
83 typename CT,
84 bool EnableDistance,
85 bool EnableAzimuth,
86 bool EnableReverseAzimuth = false,
87 bool EnableReducedLength = false,
88 bool EnableGeodesicScale = false,
89 size_t SeriesOrder = 8
90 >
91 class karney_inverse
92 {
93 static const bool CalcQuantities = EnableReducedLength || EnableGeodesicScale;
94 static const bool CalcAzimuths = EnableAzimuth || EnableReverseAzimuth || CalcQuantities;
95 static const bool CalcFwdAzimuth = EnableAzimuth || CalcQuantities;
96 static const bool CalcRevAzimuth = EnableReverseAzimuth || CalcQuantities;
97
98 public:
99 typedef result_inverse<CT> result_type;
100
101 template <typename T1, typename T2, typename Spheroid>
apply(T1 const & lo1,T1 const & la1,T2 const & lo2,T2 const & la2,Spheroid const & spheroid)102 static inline result_type apply(T1 const& lo1,
103 T1 const& la1,
104 T2 const& lo2,
105 T2 const& la2,
106 Spheroid const& spheroid)
107 {
108 static CT const c0 = 0;
109 static CT const c0_001 = 0.001;
110 static CT const c0_1 = 0.1;
111 static CT const c1 = 1;
112 static CT const c2 = 2;
113 static CT const c3 = 3;
114 static CT const c8 = 8;
115 static CT const c16 = 16;
116 static CT const c90 = 90;
117 static CT const c180 = 180;
118 static CT const c200 = 200;
119 static CT const pi = math::pi<CT>();
120 static CT const d2r = math::d2r<CT>();
121 static CT const r2d = math::r2d<CT>();
122
123 result_type result;
124
125 CT lat1 = la1;
126 CT lat2 = la2;
127
128 CT lon1 = lo1;
129 CT lon2 = lo2;
130
131 CT const a = CT(get_radius<0>(spheroid));
132 CT const b = CT(get_radius<2>(spheroid));
133 CT const f = formula::flattening<CT>(spheroid);
134 CT const one_minus_f = c1 - f;
135 CT const two_minus_f = c2 - f;
136
137 CT const tol0 = std::numeric_limits<CT>::epsilon();
138 CT const tol1 = c200 * tol0;
139 CT const tol2 = sqrt(tol0);
140
141 // Check on bisection interval.
142 CT const tol_bisection = tol0 * tol2;
143
144 CT const etol2 = c0_1 * tol2 /
145 sqrt((std::max)(c0_001, std::abs(f)) * (std::min)(c1, c1 - f / c2) / c2);
146
147 CT tiny = std::sqrt((std::numeric_limits<CT>::min)());
148
149 CT const n = f / two_minus_f;
150 CT const e2 = f * two_minus_f;
151 CT const ep2 = e2 / math::sqr(one_minus_f);
152
153 // Compute the longitudinal difference.
154 CT lon12_error;
155 CT lon12 = math::difference_angle(lon1, lon2, lon12_error);
156
157 int lon12_sign = lon12 >= 0 ? 1 : -1;
158
159 // Make points close to the meridian to lie on it.
160 lon12 = lon12_sign * lon12;
161 lon12_error = (c180 - lon12) - lon12_sign * lon12_error;
162
163 // Convert to radians.
164 CT lam12 = lon12 * d2r;
165 CT sin_lam12;
166 CT cos_lam12;
167
168 if (lon12 > c90)
169 {
170 math::sin_cos_degrees(lon12_error, sin_lam12, cos_lam12);
171 cos_lam12 *= -c1;
172 }
173 else
174 {
175 math::sin_cos_degrees(lon12, sin_lam12, cos_lam12);
176 }
177
178 // Make points close to the equator to lie on it.
179 lat1 = math::round_angle(std::abs(lat1) > c90 ? c90 : lat1);
180 lat2 = math::round_angle(std::abs(lat2) > c90 ? c90 : lat2);
181
182 // Arrange points in a canonical form, as explained in
183 // paper, Algorithms for geodesics, Eq. (44):
184 //
185 // 0 <= lon12 <= 180
186 // -90 <= lat1 <= 0
187 // lat1 <= lat2 <= -lat1
188 int swap_point = std::abs(lat1) < std::abs(lat2) ? -1 : 1;
189
190 if (swap_point < 0)
191 {
192 lon12_sign *= -1;
193 swap(lat1, lat2);
194 }
195
196 // Enforce lat1 to be <= 0.
197 int lat_sign = lat1 < 0 ? 1 : -1;
198 lat1 *= lat_sign;
199 lat2 *= lat_sign;
200
201 CT sin_beta1, cos_beta1;
202 math::sin_cos_degrees(lat1, sin_beta1, cos_beta1);
203 sin_beta1 *= one_minus_f;
204
205 math::normalize_unit_vector<CT>(sin_beta1, cos_beta1);
206 cos_beta1 = (std::max)(tiny, cos_beta1);
207
208 CT sin_beta2, cos_beta2;
209 math::sin_cos_degrees(lat2, sin_beta2, cos_beta2);
210 sin_beta2 *= one_minus_f;
211
212 math::normalize_unit_vector<CT>(sin_beta2, cos_beta2);
213 cos_beta2 = (std::max)(tiny, cos_beta2);
214
215 // If cos_beta1 < -sin_beta1, then cos_beta2 - cos_beta1 is a
216 // sensitive measure of the |beta1| - |beta2|. Alternatively,
217 // (cos_beta1 >= -sin_beta1), abs(sin_beta2) + sin_beta1 is
218 // a better measure.
219 // Sometimes these quantities vanish and in that case we
220 // force beta2 = +/- bet1a exactly.
221 if (cos_beta1 < -sin_beta1)
222 {
223 if (cos_beta1 == cos_beta2)
224 {
225 sin_beta2 = sin_beta2 < 0 ? sin_beta1 : -sin_beta1;
226 }
227 }
228 else
229 {
230 if (std::abs(sin_beta2) == -sin_beta1)
231 {
232 cos_beta2 = cos_beta1;
233 }
234 }
235
236 CT const dn1 = sqrt(c1 + ep2 * math::sqr(sin_beta1));
237 CT const dn2 = sqrt(c1 + ep2 * math::sqr(sin_beta2));
238
239 CT sigma12;
240 CT m12x, s12x, M21;
241
242 // Index zero element of coeffs_C1 is unused.
243 se::coeffs_C1<SeriesOrder, CT> const coeffs_C1(n);
244
245 bool meridian = lat1 == -90 || sin_lam12 == 0;
246
247 CT cos_alpha1, sin_alpha1;
248 CT cos_alpha2, sin_alpha2;
249
250 if (meridian)
251 {
252 // Endpoints lie on a single full meridian.
253
254 // Point to the target latitude.
255 cos_alpha1 = cos_lam12;
256 sin_alpha1 = sin_lam12;
257
258 // Heading north at the target.
259 cos_alpha2 = c1;
260 sin_alpha2 = c0;
261
262 CT sin_sigma1 = sin_beta1;
263 CT cos_sigma1 = cos_alpha1 * cos_beta1;
264
265 CT sin_sigma2 = sin_beta2;
266 CT cos_sigma2 = cos_alpha2 * cos_beta2;
267
268 CT sigma12 = std::atan2((std::max)(c0, cos_sigma1 * sin_sigma2 - sin_sigma1 * cos_sigma2),
269 cos_sigma1 * cos_sigma2 + sin_sigma1 * sin_sigma2);
270
271 CT dummy;
272 meridian_length(n, ep2, sigma12, sin_sigma1, cos_sigma1, dn1,
273 sin_sigma2, cos_sigma2, dn2,
274 cos_beta1, cos_beta2, s12x,
275 m12x, dummy, result.geodesic_scale,
276 M21, coeffs_C1);
277
278 if (sigma12 < c1 || m12x >= c0)
279 {
280 if (sigma12 < c3 * tiny)
281 {
282 sigma12 = m12x = s12x = c0;
283 }
284
285 m12x *= b;
286 s12x *= b;
287 }
288 else
289 {
290 // m12 < 0, i.e., prolate and too close to anti-podal.
291 meridian = false;
292 }
293 }
294
295 CT omega12;
296
297 if (!meridian && sin_beta1 == c0 &&
298 (f <= c0 || lon12_error >= f * c180))
299 {
300 // Points lie on the equator.
301 cos_alpha1 = cos_alpha2 = c0;
302 sin_alpha1 = sin_alpha2 = c1;
303
304 s12x = a * lam12;
305 sigma12 = omega12 = lam12 / one_minus_f;
306 m12x = b * sin(sigma12);
307
308 if (BOOST_GEOMETRY_CONDITION(EnableGeodesicScale))
309 {
310 result.geodesic_scale = cos(sigma12);
311 }
312 }
313 else if (!meridian)
314 {
315 // If point1 and point2 belong within a hemisphere bounded by a
316 // meridian and geodesic is neither meridional nor equatorial.
317
318 // Find the starting point for Newton's method.
319 CT dnm;
320 sigma12 = newton_start(sin_beta1, cos_beta1, dn1,
321 sin_beta2, cos_beta2, dn2,
322 lam12, sin_lam12, cos_lam12,
323 sin_alpha1, cos_alpha1,
324 sin_alpha2, cos_alpha2,
325 dnm, coeffs_C1, ep2,
326 tol1, tol2, etol2,
327 n, f);
328
329 if (sigma12 >= c0)
330 {
331 // Short lines case (newton_start sets sin_alpha2, cos_alpha2, dnm).
332 s12x = sigma12 * b * dnm;
333 m12x = math::sqr(dnm) * b * sin(sigma12 / dnm);
334 if (BOOST_GEOMETRY_CONDITION(EnableGeodesicScale))
335 {
336 result.geodesic_scale = cos(sigma12 / dnm);
337 }
338
339 // Convert to radians.
340 omega12 = lam12 / (one_minus_f * dnm);
341 }
342 else
343 {
344 // Apply the Newton's method.
345 CT sin_sigma1 = c0, cos_sigma1 = c0;
346 CT sin_sigma2 = c0, cos_sigma2 = c0;
347 CT eps = c0, diff_omega12 = c0;
348
349 // Bracketing range.
350 CT sin_alpha1a = tiny, cos_alpha1a = c1;
351 CT sin_alpha1b = tiny, cos_alpha1b = -c1;
352
353 size_t iteration = 0;
354 size_t max_iterations = 20 + std::numeric_limits<size_t>::digits + 10;
355
356 for (bool tripn = false, tripb = false;
357 iteration < max_iterations;
358 ++iteration)
359 {
360 CT dv;
361 CT v = lambda12(sin_beta1, cos_beta1, dn1,
362 sin_beta2, cos_beta2, dn2,
363 sin_alpha1, cos_alpha1,
364 sin_lam12, cos_lam12,
365 sin_alpha2, cos_alpha2,
366 sigma12,
367 sin_sigma1, cos_sigma1,
368 sin_sigma2, cos_sigma2,
369 eps, diff_omega12,
370 iteration < max_iterations,
371 dv, f, n, ep2, tiny, coeffs_C1);
372
373 // Reversed test to allow escape with NaNs.
374 if (tripb || !(std::abs(v) >= (tripn ? c8 : c1) * tol0))
375 break;
376
377 // Update bracketing values.
378 if (v > c0 && (iteration > max_iterations ||
379 cos_alpha1 / sin_alpha1 > cos_alpha1b / sin_alpha1b))
380 {
381 sin_alpha1b = sin_alpha1;
382 cos_alpha1b = cos_alpha1;
383 }
384 else if (v < c0 && (iteration > max_iterations ||
385 cos_alpha1 / sin_alpha1 < cos_alpha1a / sin_alpha1a))
386 {
387 sin_alpha1a = sin_alpha1;
388 cos_alpha1a = cos_alpha1;
389 }
390
391 if (iteration < max_iterations && dv > c0)
392 {
393 CT diff_alpha1 = -v / dv;
394
395 CT sin_diff_alpha1 = sin(diff_alpha1);
396 CT cos_diff_alpha1 = cos(diff_alpha1);
397
398 CT nsin_alpha1 = sin_alpha1 * cos_diff_alpha1 +
399 cos_alpha1 * sin_diff_alpha1;
400
401 if (nsin_alpha1 > c0 && std::abs(diff_alpha1) < pi)
402 {
403 cos_alpha1 = cos_alpha1 * cos_diff_alpha1 - sin_alpha1 * sin_diff_alpha1;
404 sin_alpha1 = nsin_alpha1;
405 math::normalize_unit_vector<CT>(sin_alpha1, cos_alpha1);
406
407 // In some regimes we don't get quadratic convergence because
408 // slope -> 0. So use convergence conditions based on epsilon
409 // instead of sqrt(epsilon).
410 tripn = std::abs(v) <= c16 * tol0;
411 continue;
412 }
413 }
414
415 // Either dv was not positive or updated value was outside legal
416 // range. Use the midpoint of the bracket as the next estimate.
417 // This mechanism is not needed for the WGS84 ellipsoid, but it does
418 // catch problems with more eeccentric ellipsoids. Its efficacy is
419 // such for the WGS84 test set with the starting guess set to alp1 =
420 // 90deg:
421 // the WGS84 test set: mean = 5.21, sd = 3.93, max = 24
422 // WGS84 and random input: mean = 4.74, sd = 0.99
423 sin_alpha1 = (sin_alpha1a + sin_alpha1b) / c2;
424 cos_alpha1 = (cos_alpha1a + cos_alpha1b) / c2;
425 math::normalize_unit_vector<CT>(sin_alpha1, cos_alpha1);
426 tripn = false;
427 tripb = (std::abs(sin_alpha1a - sin_alpha1) + (cos_alpha1a - cos_alpha1) < tol_bisection ||
428 std::abs(sin_alpha1 - sin_alpha1b) + (cos_alpha1 - cos_alpha1b) < tol_bisection);
429 }
430
431 CT dummy;
432 se::coeffs_C1<SeriesOrder, CT> const coeffs_C1_eps(eps);
433 // Ensure that the reduced length and geodesic scale are computed in
434 // a "canonical" way, with the I2 integral.
435 meridian_length(eps, ep2, sigma12, sin_sigma1, cos_sigma1, dn1,
436 sin_sigma2, cos_sigma2, dn2,
437 cos_beta1, cos_beta2, s12x,
438 m12x, dummy, result.geodesic_scale,
439 M21, coeffs_C1_eps);
440
441 m12x *= b;
442 s12x *= b;
443 }
444 }
445
446 if (swap_point < 0)
447 {
448 swap(sin_alpha1, sin_alpha2);
449 swap(cos_alpha1, cos_alpha2);
450 swap(result.geodesic_scale, M21);
451 }
452
453 sin_alpha1 *= swap_point * lon12_sign;
454 cos_alpha1 *= swap_point * lat_sign;
455
456 sin_alpha2 *= swap_point * lon12_sign;
457 cos_alpha2 *= swap_point * lat_sign;
458
459 if (BOOST_GEOMETRY_CONDITION(EnableReducedLength))
460 {
461 result.reduced_length = m12x;
462 }
463
464 if (BOOST_GEOMETRY_CONDITION(CalcAzimuths))
465 {
466 if (BOOST_GEOMETRY_CONDITION(CalcFwdAzimuth))
467 {
468 result.azimuth = atan2(sin_alpha1, cos_alpha1) * r2d;
469 }
470
471 if (BOOST_GEOMETRY_CONDITION(CalcRevAzimuth))
472 {
473 result.reverse_azimuth = atan2(sin_alpha2, cos_alpha2) * r2d;
474 }
475 }
476
477 if (BOOST_GEOMETRY_CONDITION(EnableDistance))
478 {
479 result.distance = s12x;
480 }
481
482 return result;
483 }
484
485 template <typename CoeffsC1>
meridian_length(CT const & epsilon,CT const & ep2,CT const & sigma12,CT const & sin_sigma1,CT const & cos_sigma1,CT const & dn1,CT const & sin_sigma2,CT const & cos_sigma2,CT const & dn2,CT const & cos_beta1,CT const & cos_beta2,CT & s12x,CT & m12x,CT & m0,CT & M12,CT & M21,CoeffsC1 const & coeffs_C1)486 static inline void meridian_length(CT const& epsilon, CT const& ep2, CT const& sigma12,
487 CT const& sin_sigma1, CT const& cos_sigma1, CT const& dn1,
488 CT const& sin_sigma2, CT const& cos_sigma2, CT const& dn2,
489 CT const& cos_beta1, CT const& cos_beta2,
490 CT& s12x, CT& m12x, CT& m0,
491 CT& M12, CT& M21,
492 CoeffsC1 const& coeffs_C1)
493 {
494 static CT const c1 = 1;
495
496 CT A12x = 0, J12 = 0;
497 CT expansion_A1, expansion_A2;
498
499 // Evaluate the coefficients for C2.
500 se::coeffs_C2<SeriesOrder, CT> coeffs_C2(epsilon);
501
502 if (BOOST_GEOMETRY_CONDITION(EnableDistance) ||
503 BOOST_GEOMETRY_CONDITION(EnableReducedLength) ||
504 BOOST_GEOMETRY_CONDITION(EnableGeodesicScale))
505 {
506 // Find the coefficients for A1 by computing the
507 // series expansion using Horner scehme.
508 expansion_A1 = se::evaluate_A1<SeriesOrder>(epsilon);
509
510 if (BOOST_GEOMETRY_CONDITION(EnableReducedLength) ||
511 BOOST_GEOMETRY_CONDITION(EnableGeodesicScale))
512 {
513 // Find the coefficients for A2 by computing the
514 // series expansion using Horner scehme.
515 expansion_A2 = se::evaluate_A2<SeriesOrder>(epsilon);
516
517 A12x = expansion_A1 - expansion_A2;
518 expansion_A2 += c1;
519 }
520 expansion_A1 += c1;
521 }
522
523 if (BOOST_GEOMETRY_CONDITION(EnableDistance))
524 {
525 CT B1 = se::sin_cos_series(sin_sigma2, cos_sigma2, coeffs_C1)
526 - se::sin_cos_series(sin_sigma1, cos_sigma1, coeffs_C1);
527
528 s12x = expansion_A1 * (sigma12 + B1);
529
530 if (BOOST_GEOMETRY_CONDITION(EnableReducedLength) ||
531 BOOST_GEOMETRY_CONDITION(EnableGeodesicScale))
532 {
533 CT B2 = se::sin_cos_series(sin_sigma2, cos_sigma2, coeffs_C2)
534 - se::sin_cos_series(sin_sigma1, cos_sigma1, coeffs_C2);
535
536 J12 = A12x * sigma12 + (expansion_A1 * B1 - expansion_A2 * B2);
537 }
538 }
539 else if (BOOST_GEOMETRY_CONDITION(EnableReducedLength) ||
540 BOOST_GEOMETRY_CONDITION(EnableGeodesicScale))
541 {
542 for (size_t i = 1; i <= SeriesOrder; ++i)
543 {
544 coeffs_C2[i] = expansion_A1 * coeffs_C1[i] -
545 expansion_A2 * coeffs_C2[i];
546 }
547
548 J12 = A12x * sigma12 +
549 (se::sin_cos_series(sin_sigma2, cos_sigma2, coeffs_C2)
550 - se::sin_cos_series(sin_sigma1, cos_sigma1, coeffs_C2));
551 }
552
553 if (BOOST_GEOMETRY_CONDITION(EnableReducedLength))
554 {
555 m0 = A12x;
556
557 m12x = dn2 * (cos_sigma1 * sin_sigma2) -
558 dn1 * (sin_sigma1 * cos_sigma2) -
559 cos_sigma1 * cos_sigma2 * J12;
560 }
561
562 if (BOOST_GEOMETRY_CONDITION(EnableGeodesicScale))
563 {
564 CT cos_sigma12 = cos_sigma1 * cos_sigma2 + sin_sigma1 * sin_sigma2;
565 CT t = ep2 * (cos_beta1 - cos_beta2) *
566 (cos_beta1 + cos_beta2) / (dn1 + dn2);
567
568 M12 = cos_sigma12 + (t * sin_sigma2 - cos_sigma2 * J12) * sin_sigma1 / dn1;
569 M21 = cos_sigma12 - (t * sin_sigma1 - cos_sigma1 * J12) * sin_sigma2 / dn2;
570 }
571 }
572
573 /*
574 Return a starting point for Newton's method in sin_alpha1 and
575 cos_alpha1 (function value is -1). If Newton's method
576 doesn't need to be used, return also sin_alpha2 and
577 cos_alpha2 and function value is sig12.
578 */
579 template <typename CoeffsC1>
newton_start(CT const & sin_beta1,CT const & cos_beta1,CT const & dn1,CT const & sin_beta2,CT const & cos_beta2,CT dn2,CT const & lam12,CT const & sin_lam12,CT const & cos_lam12,CT & sin_alpha1,CT & cos_alpha1,CT & sin_alpha2,CT & cos_alpha2,CT & dnm,CoeffsC1 const & coeffs_C1,CT const & ep2,CT const & tol1,CT const & tol2,CT const & etol2,CT const & n,CT const & f)580 static inline CT newton_start(CT const& sin_beta1, CT const& cos_beta1, CT const& dn1,
581 CT const& sin_beta2, CT const& cos_beta2, CT dn2,
582 CT const& lam12, CT const& sin_lam12, CT const& cos_lam12,
583 CT& sin_alpha1, CT& cos_alpha1,
584 CT& sin_alpha2, CT& cos_alpha2,
585 CT& dnm, CoeffsC1 const& coeffs_C1, CT const& ep2,
586 CT const& tol1, CT const& tol2, CT const& etol2, CT const& n, CT const& f)
587 {
588 static CT const c0 = 0;
589 static CT const c0_01 = 0.01;
590 static CT const c0_1 = 0.1;
591 static CT const c0_5 = 0.5;
592 static CT const c1 = 1;
593 static CT const c2 = 2;
594 static CT const c6 = 6;
595 static CT const c1000 = 1000;
596 static CT const pi = math::pi<CT>();
597
598 CT const one_minus_f = c1 - f;
599 CT const x_thresh = c1000 * tol2;
600
601 // Return a starting point for Newton's method in sin_alpha1
602 // and cos_alpha1 (function value is -1). If Newton's method
603 // doesn't need to be used, return also sin_alpha2 and
604 // cos_alpha2 and function value is sig12.
605 CT sig12 = -c1;
606
607 // bet12 = bet2 - bet1 in [0, pi); beta12a = bet2 + bet1 in (-pi, 0]
608 CT sin_beta12 = sin_beta2 * cos_beta1 - cos_beta2 * sin_beta1;
609 CT cos_beta12 = cos_beta2 * cos_beta1 + sin_beta2 * sin_beta1;
610
611 CT sin_beta12a = sin_beta2 * cos_beta1 + cos_beta2 * sin_beta1;
612
613 bool shortline = cos_beta12 >= c0 && sin_beta12 < c0_5 &&
614 cos_beta2 * lam12 < c0_5;
615
616 CT sin_omega12, cos_omega12;
617
618 if (shortline)
619 {
620 CT sin_beta_m2 = math::sqr(sin_beta1 + sin_beta2);
621
622 sin_beta_m2 /= sin_beta_m2 + math::sqr(cos_beta1 + cos_beta2);
623 dnm = math::sqrt(c1 + ep2 * sin_beta_m2);
624
625 CT omega12 = lam12 / (one_minus_f * dnm);
626
627 sin_omega12 = sin(omega12);
628 cos_omega12 = cos(omega12);
629 }
630 else
631 {
632 sin_omega12 = sin_lam12;
633 cos_omega12 = cos_lam12;
634 }
635
636 sin_alpha1 = cos_beta2 * sin_omega12;
637 cos_alpha1 = cos_omega12 >= c0 ?
638 sin_beta12 + cos_beta2 * sin_beta1 * math::sqr(sin_omega12) / (c1 + cos_omega12) :
639 sin_beta12a - cos_beta2 * sin_beta1 * math::sqr(sin_omega12) / (c1 - cos_omega12);
640
641 CT sin_sigma12 = boost::math::hypot(sin_alpha1, cos_alpha1);
642 CT cos_sigma12 = sin_beta1 * sin_beta2 + cos_beta1 * cos_beta2 * cos_omega12;
643
644 if (shortline && sin_sigma12 < etol2)
645 {
646 sin_alpha2 = cos_beta1 * sin_omega12;
647 cos_alpha2 = sin_beta12 - cos_beta1 * sin_beta2 *
648 (cos_omega12 >= c0 ? math::sqr(sin_omega12) /
649 (c1 + cos_omega12) : c1 - cos_omega12);
650
651 math::normalize_unit_vector<CT>(sin_alpha2, cos_alpha2);
652 // Set return value.
653 sig12 = atan2(sin_sigma12, cos_sigma12);
654 }
655 // Skip astroid calculation if too eccentric.
656 else if (std::abs(n) > c0_1 ||
657 cos_sigma12 >= c0 ||
658 sin_sigma12 >= c6 * std::abs(n) * pi *
659 math::sqr(cos_beta1))
660 {
661 // Nothing to do, zeroth order spherical approximation will do.
662 }
663 else
664 {
665 // Scale lam12 and bet2 to x, y coordinate system where antipodal
666 // point is at origin and singular point is at y = 0, x = -1.
667 CT lambda_scale, beta_scale;
668
669 CT y;
670 volatile CT x;
671
672 CT lam12x = atan2(-sin_lam12, -cos_lam12);
673 if (f >= c0)
674 {
675 CT k2 = math::sqr(sin_beta1) * ep2;
676 CT eps = k2 / (c2 * (c1 + sqrt(c1 + k2)) + k2);
677
678 se::coeffs_A3<SeriesOrder, CT> const coeffs_A3(n);
679
680 CT const A3 = math::horner_evaluate(eps, coeffs_A3.begin(), coeffs_A3.end());
681
682 lambda_scale = f * cos_beta1 * A3 * pi;
683 beta_scale = lambda_scale * cos_beta1;
684
685 x = lam12x / lambda_scale;
686 y = sin_beta12a / beta_scale;
687 }
688 else
689 {
690 CT cos_beta12a = cos_beta2 * cos_beta1 - sin_beta2 * sin_beta1;
691 CT beta12a = atan2(sin_beta12a, cos_beta12a);
692
693 CT m12b, m0, dummy;
694 meridian_length(n, ep2, pi + beta12a,
695 sin_beta1, -cos_beta1, dn1,
696 sin_beta2, cos_beta2, dn2,
697 cos_beta1, cos_beta2, dummy,
698 m12b, m0, dummy, dummy, coeffs_C1);
699
700 x = -c1 + m12b / (cos_beta1 * cos_beta2 * m0 * pi);
701 beta_scale = x < -c0_01
702 ? sin_beta12a / x
703 : -f * math::sqr(cos_beta1) * pi;
704 lambda_scale = beta_scale / cos_beta1;
705
706 y = lam12x / lambda_scale;
707 }
708
709 if (y > -tol1 && x > -c1 - x_thresh)
710 {
711 // Strip near cut.
712 if (f >= c0)
713 {
714 sin_alpha1 = (std::min)(c1, -CT(x));
715 cos_alpha1 = - math::sqrt(c1 - math::sqr(sin_alpha1));
716 }
717 else
718 {
719 cos_alpha1 = (std::max)(CT(x > -tol1 ? c0 : -c1), CT(x));
720 sin_alpha1 = math::sqrt(c1 - math::sqr(cos_alpha1));
721 }
722 }
723 else
724 {
725 // Solve the astroid problem.
726 CT k = astroid(CT(x), y);
727
728 CT omega12a = lambda_scale * (f >= c0 ? -x * k /
729 (c1 + k) : -y * (c1 + k) / k);
730
731 sin_omega12 = sin(omega12a);
732 cos_omega12 = -cos(omega12a);
733
734 // Update spherical estimate of alpha1 using omgega12 instead of lam12.
735 sin_alpha1 = cos_beta2 * sin_omega12;
736 cos_alpha1 = sin_beta12a - cos_beta2 * sin_beta1 *
737 math::sqr(sin_omega12) / (c1 - cos_omega12);
738 }
739 }
740
741 // Sanity check on starting guess. Backwards check allows NaN through.
742 if (!(sin_alpha1 <= c0))
743 {
744 math::normalize_unit_vector<CT>(sin_alpha1, cos_alpha1);
745 }
746 else
747 {
748 sin_alpha1 = c1;
749 cos_alpha1 = c0;
750 }
751
752 return sig12;
753 }
754
755 /*
756 Solve the astroid problem using the equation:
757 κ4 + 2κ3 + (1 − x2 − y 2 )κ2 − 2y 2 κ − y 2 = 0.
758
759 For details, please refer to Eq. (65) in,
760 Geodesics on an ellipsoid of revolution, Charles F.F Karney,
761 https://arxiv.org/abs/1102.1215
762 */
astroid(CT const & x,CT const & y)763 static inline CT astroid(CT const& x, CT const& y)
764 {
765 static CT const c0 = 0;
766 static CT const c1 = 1;
767 static CT const c2 = 2;
768 static CT const c3 = 3;
769 static CT const c4 = 4;
770 static CT const c6 = 6;
771
772 CT k;
773
774 CT p = math::sqr(x);
775 CT q = math::sqr(y);
776 CT r = (p + q - c1) / c6;
777
778 if (!(q == c0 && r <= c0))
779 {
780 // Avoid possible division by zero when r = 0 by multiplying
781 // equations for s and t by r^3 and r, respectively.
782 CT S = p * q / c4;
783 CT r2 = math::sqr(r);
784 CT r3 = r * r2;
785
786 // The discriminant of the quadratic equation for T3. This is
787 // zero on the evolute curve p^(1/3)+q^(1/3) = 1.
788 CT discriminant = S * (S + c2 * r3);
789
790 CT u = r;
791
792 if (discriminant >= c0)
793 {
794 CT T3 = S + r3;
795
796 // Pick the sign on the sqrt to maximize abs(T3). This minimizes
797 // loss of precision due to cancellation. The result is unchanged
798 // because of the way the T is used in definition of u.
799 T3 += T3 < c0 ? -std::sqrt(discriminant) : std::sqrt(discriminant);
800
801 CT T = std::cbrt(T3);
802
803 // T can be zero; but then r2 / T -> 0.
804 u += T + (T != c0 ? r2 / T : c0);
805 }
806 else
807 {
808 CT ang = std::atan2(std::sqrt(-discriminant), -(S + r3));
809
810 // There are three possible cube roots. We choose the root which avoids
811 // cancellation. Note that discriminant < 0 implies that r < 0.
812 u += c2 * r * cos(ang / c3);
813 }
814
815 CT v = std::sqrt(math::sqr(u) + q);
816
817 // Avoid loss of accuracy when u < 0.
818 CT uv = u < c0 ? q / (v - u) : u + v;
819 CT w = (uv - q) / (c2 * v);
820
821 // Rearrange expression for k to avoid loss of accuracy due to
822 // subtraction. Division by 0 not possible because uv > 0, w >= 0.
823 k = uv / (std::sqrt(uv + math::sqr(w)) + w);
824 }
825 else // q == 0 && r <= 0
826 {
827 // y = 0 with |x| <= 1. Handle this case directly.
828 // For y small, positive root is k = abs(y)/sqrt(1-x^2).
829 k = c0;
830 }
831 return k;
832 }
833
834 template <typename CoeffsC1>
lambda12(CT const & sin_beta1,CT const & cos_beta1,CT const & dn1,CT const & sin_beta2,CT const & cos_beta2,CT const & dn2,CT const & sin_alpha1,CT cos_alpha1,CT const & sin_lam120,CT const & cos_lam120,CT & sin_alpha2,CT & cos_alpha2,CT & sigma12,CT & sin_sigma1,CT & cos_sigma1,CT & sin_sigma2,CT & cos_sigma2,CT & eps,CT & diff_omega12,bool diffp,CT & diff_lam12,CT const & f,CT const & n,CT const & ep2,CT const & tiny,CoeffsC1 const & coeffs_C1)835 static inline CT lambda12(CT const& sin_beta1, CT const& cos_beta1, CT const& dn1,
836 CT const& sin_beta2, CT const& cos_beta2, CT const& dn2,
837 CT const& sin_alpha1, CT cos_alpha1,
838 CT const& sin_lam120, CT const& cos_lam120,
839 CT& sin_alpha2, CT& cos_alpha2,
840 CT& sigma12,
841 CT& sin_sigma1, CT& cos_sigma1,
842 CT& sin_sigma2, CT& cos_sigma2,
843 CT& eps, CT& diff_omega12,
844 bool diffp, CT& diff_lam12,
845 CT const& f, CT const& n, CT const& ep2, CT const& tiny,
846 CoeffsC1 const& coeffs_C1)
847 {
848 static CT const c0 = 0;
849 static CT const c1 = 1;
850 static CT const c2 = 2;
851
852 CT const one_minus_f = c1 - f;
853
854 if (sin_beta1 == c0 && cos_alpha1 == c0)
855 {
856 // Break degeneracy of equatorial line.
857 cos_alpha1 = -tiny;
858 }
859
860
861 CT sin_alpha0 = sin_alpha1 * cos_beta1;
862 CT cos_alpha0 = boost::math::hypot(cos_alpha1, sin_alpha1 * sin_beta1);
863
864 CT sin_omega1, cos_omega1;
865 CT sin_omega2, cos_omega2;
866 CT sin_omega12, cos_omega12;
867
868 CT lam12;
869
870 sin_sigma1 = sin_beta1;
871 sin_omega1 = sin_alpha0 * sin_beta1;
872
873 cos_sigma1 = cos_omega1 = cos_alpha1 * cos_beta1;
874
875 math::normalize_unit_vector<CT>(sin_sigma1, cos_sigma1);
876
877 // Enforce symmetries in the case abs(beta2) = -beta1.
878 // Otherwise, this can yield singularities in the Newton iteration.
879
880 // sin(alpha2) * cos(beta2) = sin(alpha0).
881 sin_alpha2 = cos_beta2 != cos_beta1 ?
882 sin_alpha0 / cos_beta2 : sin_alpha1;
883
884 cos_alpha2 = cos_beta2 != cos_beta1 || std::abs(sin_beta2) != -sin_beta1 ?
885 sqrt(math::sqr(cos_alpha1 * cos_beta1) +
886 (cos_beta1 < -sin_beta1 ?
887 (cos_beta2 - cos_beta1) * (cos_beta1 + cos_beta2) :
888 (sin_beta1 - sin_beta2) * (sin_beta1 + sin_beta2))) / cos_beta2 :
889 std::abs(cos_alpha1);
890
891 sin_sigma2 = sin_beta2;
892 sin_omega2 = sin_alpha0 * sin_beta2;
893
894 cos_sigma2 = cos_omega2 =
895 (cos_alpha2 * cos_beta2);
896
897 // Break degeneracy of equatorial line.
898 math::normalize_unit_vector<CT>(sin_sigma2, cos_sigma2);
899
900
901 // sig12 = sig2 - sig1, limit to [0, pi].
902 sigma12 = atan2((std::max)(c0, cos_sigma1 * sin_sigma2 - sin_sigma1 * cos_sigma2),
903 cos_sigma1 * cos_sigma2 + sin_sigma1 * sin_sigma2);
904
905 // omg12 = omg2 - omg1, limit to [0, pi].
906 sin_omega12 = (std::max)(c0, cos_omega1 * sin_omega2 - sin_omega1 * cos_omega2);
907 cos_omega12 = cos_omega1 * cos_omega2 + sin_omega1 * sin_omega2;
908
909 // eta = omg12 - lam120.
910 CT eta = atan2(sin_omega12 * cos_lam120 - cos_omega12 * sin_lam120,
911 cos_omega12 * cos_lam120 + sin_omega12 * sin_lam120);
912
913 CT B312;
914 CT k2 = math::sqr(cos_alpha0) * ep2;
915
916 eps = k2 / (c2 * (c1 + std::sqrt(c1 + k2)) + k2);
917
918 se::coeffs_C3<SeriesOrder, CT> const coeffs_C3(n, eps);
919
920 B312 = se::sin_cos_series(sin_sigma2, cos_sigma2, coeffs_C3)
921 - se::sin_cos_series(sin_sigma1, cos_sigma1, coeffs_C3);
922
923 se::coeffs_A3<SeriesOrder, CT> const coeffs_A3(n);
924
925 CT const A3 = math::horner_evaluate(eps, coeffs_A3.begin(), coeffs_A3.end());
926
927 diff_omega12 = -f * A3 * sin_alpha0 * (sigma12 + B312);
928 lam12 = eta + diff_omega12;
929
930 if (diffp)
931 {
932 if (cos_alpha2 == c0)
933 {
934 diff_lam12 = - c2 * one_minus_f * dn1 / sin_beta1;
935 }
936 else
937 {
938 CT dummy;
939 meridian_length(eps, ep2, sigma12, sin_sigma1, cos_sigma1, dn1,
940 sin_sigma2, cos_sigma2, dn2,
941 cos_beta1, cos_beta2, dummy,
942 diff_lam12, dummy, dummy,
943 dummy, coeffs_C1);
944
945 diff_lam12 *= one_minus_f / (cos_alpha2 * cos_beta2);
946 }
947 }
948 return lam12;
949 }
950
951 };
952
953 }}} // namespace boost::geometry::formula
954
955
956 #endif // BOOST_GEOMETRY_FORMULAS_KARNEY_INVERSE_HPP
957