GeographicLib 2.3
Rhumb.cpp
Go to the documentation of this file.
1/**
2 * \file Rhumb.cpp
3 * \brief Implementation for GeographicLib::Rhumb and GeographicLib::RhumbLine
4 * classes
5 *
6 * Copyright (c) Charles Karney (2014-2023) <karney@alum.mit.edu> and licensed
7 * under the MIT/X11 License. For more information, see
8 * https://geographiclib.sourceforge.io/
9 **********************************************************************/
10
12#include <GeographicLib/DST.hpp>
13
14#if defined(_MSC_VER)
15// Squelch warnings about enum-float expressions
16# pragma warning (disable: 5055)
17#endif
18
19namespace GeographicLib {
20
21 using namespace std;
22
23 Rhumb::Rhumb(real a, real f, bool exact)
24 : _aux(a, f)
25 , _exact(exact)
26 , _a(a)
27 , _f(f)
28 , _n(_f / (2 - _f))
29 , _rm(_aux.RectifyingRadius(_exact))
30 , _c2(_aux.AuthalicRadiusSquared(_exact) * Math::degree())
31 , _lL(_exact ? 8 : Lmax_) // 8 is starting size for DFT fit
32 , _pP(_lL)
33 {
34 AreaCoeffs();
35 }
36
38 static const Rhumb
40 return wgs84;
41 }
42
43 void Rhumb::AreaCoeffs() {
44 // Set up coefficients for area calculation
45 if (_exact) {
46 // Compute coefficients by Fourier transform of integrand
47 static const real eps = numeric_limits<real>::epsilon()/2;
48 qIntegrand f(_aux);
49 int L = 4;
50 vector<real> c(L);
51 DST fft(L); fft.transform(f, c.data()); L *= 2;
52 // For |n| <= 0.99, actual max for doubles is 2163. This scales as
53 // Math::digits() and for long doubles (GEOGRAPHICLIB_PRECISION = 3,
54 // digits = 64), this becomes 2163 * 64 / 53 = 2612. Round this up to
55 // 2^12 = 4096 and scale this by Math::digits()/64 if digits() > 64.
56 //
57 // 64 = digits for long double, 6 = 12 - log2(64)
58 int Lmax = 1<<(int(ceil(log2(max(Math::digits(), 64)))) + 6);
59 for (_lL = 0; L <= Lmax && _lL == 0; L *=2) {
60 fft.reset(L/2); c.resize(L); fft.refine(f, c.data());
61 _pP.resize(L);
62 for (int l = 0, k = -1; l < L; ++l) {
63 // Compute Fourier coefficients of integral
64 _pP[l] = (c[l] + (l+1 < L ? c[l+1] : 0)) / (-4 * (l+1));
65 if (fabs(_pP[l]) <= eps) {
66 if (k < 0) k = l; // mark as first small value
67 } else
68 k = -1; // run interrupted
69 if (k >= 0 && l - k + 1 >= (l + 1 + 7) / 8) {
70 // run of small values of at least l/8?
71 _lL = l + 1; _pP.resize(_lL); break;
72 }
73 }
74 // loop exits if _lL > 0
75 }
76 if (_lL == 0) // Hasn't converged -- just use the values we have
77 _lL = int(_pP.size());
78 } else {
79 // Use series expansions in n for Fourier coeffients of the integral
80 // See "Series expansions for computing rhumb areas"
81 // https://doi.org/10.5281/zenodo.7685484
82#if GEOGRAPHICLIB_RHUMBAREA_ORDER == 4
83 static const real coeffs[] = {
84 // Coefficients in matrix Q
85 596/real(2025), -398/real(945), 22/real(45), -1/real(3),
86 1543/real(4725), -118/real(315), 1/real(5),
87 152/real(945), -17/real(315),
88 5/real(252),
89 };
90#elif GEOGRAPHICLIB_RHUMBAREA_ORDER == 5
91 static const real coeffs[] = {
92 // Coefficients in matrix Q
93 -102614/real(467775), 596/real(2025), -398/real(945), 22/real(45),
94 -1/real(3),
95 -24562/real(155925), 1543/real(4725), -118/real(315), 1/real(5),
96 -38068/real(155925), 152/real(945), -17/real(315),
97 -752/real(10395), 5/real(252),
98 -101/real(17325),
99 };
100#elif GEOGRAPHICLIB_RHUMBAREA_ORDER == 6
101 static const real coeffs[] = {
102 // Coefficients in matrix Q
103 138734126/real(638512875), -102614/real(467775), 596/real(2025),
104 -398/real(945), 22/real(45), -1/real(3),
105 17749373/real(425675250), -24562/real(155925), 1543/real(4725),
106 -118/real(315), 1/real(5),
107 1882432/real(8513505), -38068/real(155925), 152/real(945),
108 -17/real(315),
109 268864/real(2027025), -752/real(10395), 5/real(252),
110 62464/real(2027025), -101/real(17325),
111 11537/real(4054050),
112 };
113#elif GEOGRAPHICLIB_RHUMBAREA_ORDER == 7
114 static const real coeffs[] = {
115 // Coefficients in matrix Q
116 -565017322/real(1915538625), 138734126/real(638512875),
117 -102614/real(467775), 596/real(2025), -398/real(945), 22/real(45),
118 -1/real(3),
119 -1969276/real(58046625), 17749373/real(425675250), -24562/real(155925),
120 1543/real(4725), -118/real(315), 1/real(5),
121 -58573784/real(638512875), 1882432/real(8513505), -38068/real(155925),
122 152/real(945), -17/real(315),
123 -6975184/real(42567525), 268864/real(2027025), -752/real(10395),
124 5/real(252),
125 -112832/real(1447875), 62464/real(2027025), -101/real(17325),
126 -4096/real(289575), 11537/real(4054050),
127 -311/real(525525),
128 };
129#elif GEOGRAPHICLIB_RHUMBAREA_ORDER == 8
130 static const real coeffs[] = {
131 // Coefficients in matrix Q
132 188270561816LL/real(488462349375LL), -565017322/real(1915538625),
133 138734126/real(638512875), -102614/real(467775), 596/real(2025),
134 -398/real(945), 22/real(45), -1/real(3),
135 2332829602LL/real(23260111875LL), -1969276/real(58046625),
136 17749373/real(425675250), -24562/real(155925), 1543/real(4725),
137 -118/real(315), 1/real(5),
138 -41570288/real(930404475), -58573784/real(638512875),
139 1882432/real(8513505), -38068/real(155925), 152/real(945),
140 -17/real(315),
141 1538774036/real(10854718875LL), -6975184/real(42567525),
142 268864/real(2027025), -752/real(10395), 5/real(252),
143 436821248/real(3618239625LL), -112832/real(1447875),
144 62464/real(2027025), -101/real(17325),
145 3059776/real(80405325), -4096/real(289575), 11537/real(4054050),
146 4193792/real(723647925), -311/real(525525),
147 1097653/real(1929727800),
148 };
149#else
150#error "Bad value for GEOGRAPHICLIB_RHUMBAREA_ORDER"
151#endif
152
153 static_assert(sizeof(coeffs) / sizeof(real) ==
154 (Lmax_ * (Lmax_ + 1))/2,
155 "Coefficient array size mismatch for Rhumb");
156 real d = 1;
157 int o = 0;
158 for (int l = 0; l < Lmax_; ++l) {
159 int m = Lmax_ - l - 1;
160 d *= _n;
161 _pP[l] = d * Math::polyval(m, coeffs + o, _n);
162 o += m + 1;
163 }
164 }
165 // Post condition: o == sizeof(coeffs) / sizeof(real)
166 }
167
168 Rhumb::qIntegrand::qIntegrand(const AuxLatitude& aux)
169 : _aux(aux) {}
170
171 Math::real Rhumb::qIntegrand::operator()(real beta) const {
172 // pbeta(beta) = integrate(q(beta), beta)
173 // q(beta) = (1-f) * (sin(xi) - sin(chi)) / cos(phi)
174 // = (1-f) * (cos(chi) - cos(xi)) / cos(phi) *
175 // (cos(xi) + cos(chi)) / (sin(xi) + sin(chi))
176 // Fit q(beta)/cos(beta) with Fourier transform
177 // q(beta)/cos(beta) = sum(c[k] * sin((2*k+1)*beta), k, 0, K-1)
178 // then the integral is
179 // pbeta = sum(d[k] * cos((2*k+2)*beta), k, 0, K-1)
180 // where
181 // d[k] = -1/(4*(k+1)) * (c[k] + c[k+1]) for k in 0..K-2
182 // d[K-1] = -1/(4*K) * c[K-1]
183 AuxAngle betaa(AuxAngle::radians(beta)),
184 phia(_aux.Convert(AuxLatitude::BETA, AuxLatitude::PHI,
185 betaa, true).normalized()),
186 chia(_aux.Convert(AuxLatitude::PHI , AuxLatitude::CHI,
187 phia , true).normalized()),
188 xia (_aux.Convert(AuxLatitude::PHI , AuxLatitude::XI ,
189 phia , true).normalized());
190 real schi = chia.y(), cchi = chia.x(), sxi = xia.y(), cxi = xia.x(),
191 cphi = phia.x(), cbeta = betaa.x();
192 return (1 - _aux.Flattening()) *
193 ( fabs(schi) < fabs(cchi) ? sxi - schi :
194 (cchi - cxi) * (cxi + cchi) / (sxi + schi) ) / (cphi * cbeta);
195 // Value for beta = pi/2. This isn't needed because beta is given in
196 // radians and cos(pi/2) is never exactly 0. See formulas in the auxlat
197 // paper for tan(chi)/tan(phi) and tan(xi)/tan(phi) in the limit phi ->
198 // pi/2.
199 //
200 // n = 1/2;
201 // e = 2*sqrt(n) / (1 + n);
202 // e1 = 2*sqrt(n) / (1 - n);
203 // at = f == 0 ? 1 : (f < 0 ? atan(|e|)/|e| : asinh(e1)/e);
204 // q1 = at + 1 / (1 - e^2);
205 // s1 = sinh(e^2 * at);
206 // h1 = f < 0 ? hypot(1, s1) - s1 : 1/(hypot(1, s1) + s1);
207 // v = (1 - e^2) / (2 * h1^2) - 1 / ((1 - e^2) * q1);
208 }
209
210 void Rhumb::GenInverse(real lat1, real lon1, real lat2, real lon2,
211 unsigned outmask,
212 real& s12, real& azi12, real& S12) const {
213 using std::isinf; // Needed for Centos 7, ubuntu 14
214 AuxAngle phi1(AuxAngle::degrees(lat1)), phi2(AuxAngle::degrees(lat2)),
215 chi1(_aux.Convert(_aux.PHI, _aux.CHI, phi1, _exact)),
216 chi2(_aux.Convert(_aux.PHI, _aux.CHI, phi2, _exact));
217 real
218 lon12 = Math::AngDiff(lon1, lon2),
219 lam12 = lon12 * Math::degree<real>(),
220 psi1 = chi1.lam(),
221 psi2 = chi2.lam(),
222 psi12 = psi2 - psi1;
223 if (outmask & AZIMUTH)
224 azi12 = Math::atan2d(lam12, psi12);
225 if (outmask & DISTANCE) {
226 if (isinf(psi1) || isinf(psi2)) {
228 phi2, _exact).radians() -
230 phi1, _exact).radians()) * _rm;
231 } else {
232 real h = hypot(lam12, psi12);
233 // dmu/dpsi = dmu/dchi / dpsi/dchi
234 real dmudpsi = _exact ?
235 _aux.DRectifying(phi1, phi2) / _aux.DIsometric(phi1, phi2) :
236 _aux.DConvert(AuxLatitude::CHI, AuxLatitude::MU, chi1, chi2)
237 / DAuxLatitude::Dlam(chi1.tan(), chi2.tan());
238 s12 = h * dmudpsi * _rm;
239 }
240 }
241 if (outmask & AREA)
242 S12 = _c2 * lon12 * MeanSinXi(chi1, chi2);
243 }
244
245 RhumbLine Rhumb::Line(real lat1, real lon1, real azi12) const
246 { return RhumbLine(*this, lat1, lon1, azi12); }
247
248 void Rhumb::GenDirect(real lat1, real lon1, real azi12, real s12,
249 unsigned outmask,
250 real& lat2, real& lon2, real& S12) const
251 { Line(lat1, lon1, azi12).GenPosition(s12, outmask, lat2, lon2, S12); }
252
253 Math::real Rhumb::MeanSinXi(const AuxAngle& chix, const AuxAngle& chiy)
254 const {
256 phix (_aux.Convert(_aux.CHI, _aux.PHI , chix, _exact)),
257 phiy (_aux.Convert(_aux.CHI, _aux.PHI , chiy, _exact)),
258 betax(_aux.Convert(_aux.PHI, _aux.BETA, phix, _exact).normalized()),
259 betay(_aux.Convert(_aux.PHI, _aux.BETA, phiy, _exact).normalized());
260 real DpbetaDbeta =
262 betay.radians() - betax.radians(),
263 betax.y(), betax.x(), betay.y(), betay.x(),
264 _pP.data(), _lL),
265 tx = chix.tan(), ty = chiy.tan(),
266 DbetaDpsi = _exact ?
267 _aux.DParametric(phix, phiy) / _aux.DIsometric(phix, phiy) :
268 _aux.DConvert(AuxLatitude::CHI, AuxLatitude::BETA, chix, chiy) /
269 DAuxLatitude::Dlam(tx, ty);
270 return DAuxLatitude::Dp0Dpsi(tx, ty) + DpbetaDbeta * DbetaDpsi;
271 }
272
273 RhumbLine::RhumbLine(const Rhumb& rh, real lat1, real lon1, real azi12)
274 : _rh(rh)
275 , _lat1(Math::LatFix(lat1))
276 , _lon1(lon1)
277 , _azi12(Math::AngNormalize(azi12))
278 {
279 Math::sincosd(_azi12, _salp, _calp);
280 _phi1 = AuxAngle::degrees(lat1);
281 _mu1 = _rh._aux.Convert(AuxLatitude::PHI, AuxLatitude::MU,
282 _phi1, _rh._exact).degrees();
283 _chi1 = _rh._aux.Convert(AuxLatitude::PHI, AuxLatitude::CHI,
284 _phi1, _rh._exact);
285 _psi1 = _chi1.lam();
286 }
287
288 void RhumbLine::GenPosition(real s12, unsigned outmask,
289 real& lat2, real& lon2, real& S12) const {
290 real
291 r12 = s12 / (_rh._rm * Math::degree()), // scaled distance in degrees
292 mu12 = r12 * _calp,
293 mu2 = _mu1 + mu12;
294 real lat2x, lon2x;
295 if (fabs(mu2) <= Math::qd) {
296 AuxAngle mu2a(AuxAngle::degrees(mu2)),
298 mu2a, _rh._exact)),
300 phi2, _rh._exact));
301 lat2x = phi2.degrees();
302 real dmudpsi = _rh._exact ?
303 _rh._aux.DRectifying(_phi1, phi2) / _rh._aux.DIsometric(_phi1, phi2) :
304 _rh._aux.DConvert(AuxLatitude::CHI, AuxLatitude::MU, _chi1, chi2)
305 / DAuxLatitude::Dlam(_chi1.tan(), chi2.tan());
306 lon2x = r12 * _salp / dmudpsi;
307 if (outmask & AREA)
308 S12 = _rh._c2 * lon2x * _rh.MeanSinXi(_chi1, chi2);
309 lon2x = outmask & LONG_UNROLL ? _lon1 + lon2x :
311 } else {
312 // Reduce to the interval [-180, 180)
313 mu2 = Math::AngNormalize(mu2);
314 // Deal with points on the anti-meridian
315 if (fabs(mu2) > Math::qd) mu2 = Math::AngNormalize(Math::hd - mu2);
316 lat2x = _rh._aux.Convert(AuxLatitude::MU, AuxLatitude::PHI,
317 AuxAngle::degrees(mu2), _rh._exact).degrees();
318 lon2x = Math::NaN();
319 if (outmask & AREA)
320 S12 = Math::NaN();
321 }
322 if (outmask & LATITUDE) lat2 = lat2x;
323 if (outmask & LONGITUDE) lon2 = lon2x;
324 }
325
326} // namespace GeographicLib
Header for GeographicLib::DST class.
GeographicLib::Math::real real
Definition: GeodSolve.cpp:29
Header for GeographicLib::Rhumb and GeographicLib::RhumbLine classes.
An accurate representation of angles.
Definition: AuxAngle.hpp:47
Math::real radians() const
Definition: AuxAngle.hpp:228
AuxAngle normalized() const
Definition: AuxAngle.cpp:31
Math::real degrees() const
Definition: AuxAngle.hpp:224
Math::real lam() const
Definition: AuxAngle.hpp:232
Math::real tan() const
Definition: AuxAngle.hpp:113
AuxAngle Convert(int auxin, int auxout, const AuxAngle &zeta, bool exact=false) const
static Math::real Dlam(real x, real y)
Math::real DParametric(const AuxAngle &phi1, const AuxAngle &phi2) const
Math::real DConvert(int auxin, int auxout, const AuxAngle &zeta1, const AuxAngle &zeta2) const
Math::real DIsometric(const AuxAngle &phi1, const AuxAngle &phi2) const
static Math::real Dp0Dpsi(real x, real y)
static Math::real DClenshaw(bool sinp, real Delta, real szeta1, real czeta1, real szeta2, real czeta2, const real c[], int K)
Math::real DRectifying(const AuxAngle &phi1, const AuxAngle &phi2) const
Discrete sine transforms.
Definition: DST.hpp:63
Mathematical functions needed by GeographicLib.
Definition: Math.hpp:77
static T degree()
Definition: Math.hpp:193
static void sincosd(T x, T &sinx, T &cosx)
Definition: Math.cpp:106
static T atan2d(T y, T x)
Definition: Math.cpp:183
static T AngNormalize(T x)
Definition: Math.cpp:71
static int digits()
Definition: Math.cpp:26
static T NaN()
Definition: Math.cpp:250
static T polyval(int N, const T p[], T x)
Definition: Math.hpp:264
static T AngDiff(T x, T y, T &e)
Definition: Math.cpp:82
@ hd
degrees per half turn
Definition: Math.hpp:141
@ qd
degrees per quarter turn
Definition: Math.hpp:138
Find a sequence of points on a single rhumb line.
Definition: Rhumb.hpp:381
void GenPosition(real s12, unsigned outmask, real &lat2, real &lon2, real &S12) const
Definition: Rhumb.cpp:288
Solve of the direct and inverse rhumb problems.
Definition: Rhumb.hpp:79
RhumbLine Line(real lat1, real lon1, real azi12) const
Definition: Rhumb.cpp:245
friend class RhumbLine
Definition: Rhumb.hpp:82
Rhumb(real a, real f, bool exact=false)
Definition: Rhumb.cpp:23
static const Rhumb & WGS84()
Definition: Rhumb.cpp:37
Namespace for GeographicLib.
Definition: Accumulator.cpp:12