GeographicLib  2.6
Conformal3.cpp
Go to the documentation of this file.
1 /**
2  * \file Conformal3.cpp
3  * \brief Implementation for GeographicLib::Triaxial::Conformal3 class
4  *
5  * Copyright (c) Charles Karney (2014-2025) <karney@alum.mit.edu> and licensed
6  * under the MIT/X11 License. For more information, see
7  * https://geographiclib.sourceforge.io/
8  **********************************************************************/
9 
10 #include <iostream>
11 #include <iomanip>
14 
15 namespace GeographicLib {
16  namespace Triaxial {
17 
18  using namespace std;
19 
21  : _t(t)
22  , _ex(kp2() * (1 - e2() * k2 ()), - e2() * kp2(),
23  k2 () * (1 + e2() * kp2()), 1 + e2() * kp2())
24  , _ey(k2 () * (1 + e2() * kp2()), e2() * k2 (),
25  kp2() * (1 - e2() * k2 ()), 1 - e2() * k2 ())
26  , _x( Math::sq(a()) / b() * _ex.Pi() )
27  , _y( Math::sq(c()) / b() * _ey.Pi() )
28  {
29  _s = EquivSphere(_x, _y, _exs, _eys);
30  }
31  // Proving the equivalence of this and the Nyrtsov formulations is
32  // straightforward but laborious. Working from this formulation:
33  // * switch the angle argument of Pi to its complement
34  // * sin(phi)^2 -> cos(phi')^2 = 1 - sin(phi')^2
35  // * modulus of Pi then becomes imaginary
36  // * Use DLMF 19.7.5 to express as sum of Pi term and F term
37 
38  Conformal3::Conformal3(real a, real b, real c)
39  : Conformal3(Ellipsoid3(a, b, c))
40  {}
41  Conformal3::Conformal3(real b, real e2, real k2, real kp2)
42  : Conformal3(Ellipsoid3(b, e2, k2, kp2))
43  {}
44 
45  Math::real Conformal3::Pi(const EllipticFunction& ell, ang phi) {
46  if (ell.kp2() == 0 && (signbit(phi.c()) || phi.n() != 0))
47  return Math::NaN();
48  real p = ell.Pi(phi.s(), phi.c(), ell.Delta(phi.s(), phi.c()));
49  if (phi.n() != 0) p += 4 * ell.Pi() * phi.n();
50  return p;
51  }
52  Angle Conformal3::Piinv(const EllipticFunction& ell, real x) {
53  real y, n;
54  if (ell.kp2() == 0) {
55  // ell.Pi() == inf
56  y = x; n = 0;
57  } else {
58  y = remainder(x, 2 * ell.Pi());
59  n = 2 * round((x - y) / (2 * ell.Pi()));
60  }
61  // Now x = n * Pi() + y where y in [-Pi(), Pi()]. Pi() is the quarter
62  // period for the elliptic integral which corresponds to pi/2 in angle
63  // space.
64  if (y == 0)
65  return ang::cardinal( n == 0 ? y : n ); // Preserve the sign of +/-0
66  else if (fabs(y) == ell.Pi())
67  return ang::cardinal(copysign(real(1), y) + n);
68  else {
69  // solve Pi(phi) = y for phi
70  // Pi'(phi) = 1/(sqrt(1 - ell.k2() * Math::sq(sin(phi)))
71  // * (1 - alpha2 * Math::sq(sin(phi))))
72  // For k2 in [0,1]
73  // 1 - ell.k2() * Math::sq(sin(phi))
74  // = ell.kp2() + ell.k2() * Math::sq(cos(phi))
75  // For alpha2 > 0
76  // 1 - alpha2 * Math::sq(sin(phi))
77  // alphap2 + alpha2 * Math::sq(sin(phi))
78  int countn = 0, countb = 0;
79  auto Pif = [&ell]
80  (real phi) -> pair<real, real>
81  {
82  real s = sin(phi), c = cos(phi),
83  f = ell.Pi(s, c, ell.Delta(s, c)),
84  fp = 1 / (sqrt(ell.kp2() + ell.k2() * c*c) *
85  (ell.alpha2() >= 0 ?
86  ell.alphap2() + ell.alpha2() * s*s :
87  1 - ell.alpha2() * c*c));
88  return pair<real, real>(f, fp);
89  };
90  real z = Trigfun::root(Trigfun::PIINV,
91  Pif, fabs(y), fabs(y) * Math::pi()/(2*ell.Pi()),
92  0, Math::pi()/2,
93  1,1,1,
94  &countn, &countb);
95  (void) countn; (void) countb;
96  // cout << "CNT " << countn << " " << countb << "\n";
97  return ang::radians(copysign(z, y)) + ang::cardinal(n);
98  }
99  }
100  Math::real Conformal3::F(const EllipticFunction& ell, ang phi) {
101  if (ell.kp2() == 0 && (signbit(phi.c()) || phi.n() != 0))
102  return Math::NaN();
103  real p = ell.F(phi.s(), phi.c(), ell.Delta(phi.s(), phi.c()));
104  if (phi.n() != 0) p += 4 * ell.K() * phi.n();
105  return p;
106  }
107  Angle Conformal3::Finv(const EllipticFunction& ell, real x) {
108  real y, n;
109  if (ell.kp2() == 0) {
110  // ell.K() == inf
111  y = x; n = 0;
112  } else {
113  y = remainder(x, 2 * ell.K());
114  n = 2 * round((x - y) / (2 * ell.K()));
115  }
116  // Now x = n * K() + y where y in [-K(), K()]. K() is the quarter
117  // period for the elliptic integral which corresponds to pi/2 in angle
118  // space.
119  if (y == 0)
120  return ang::cardinal( n == 0 ? y : n ); // Preserve the sign of +/-0
121  else if (fabs(y) == ell.K()) // inf == inf is true
122  return ang::cardinal(copysign(real(1), y) + n);
123  else {
124  // solve F(phi) = y for phi
125  // F'(phi) = 1/sqrt(1 - ell.k2() * Math::sq(sin(phi)))
126  // For k2 in [0,1]
127  // 1 - ell.k2() * Math::sq(sin(phi))
128  // = ell.kp2() + ell.k2() * Math::sq(cos(phi))
129  int countn = 0, countb = 0;
130  auto Ff = [&ell]
131  (real phi) -> pair<real, real>
132  {
133  real s = sin(phi), c = cos(phi),
134  f = ell.F(s, c, ell.Delta(s, c)),
135  fp = 1 / sqrt(ell.kp2() + ell.k2() * c*c);
136  return pair<real, real>(f, fp);
137  };
138  real z = Trigfun::root(Trigfun::FINV,
139  Ff, fabs(y), fabs(y) * Math::pi()/(2*ell.K()),
140  0, Math::pi()/2,
141  1,1,1,
142  &countn, &countb);
143  (void) countn; (void) countb;
144  // cout << "CNT " << countn << " " << countb << "\n";
145  return ang::radians(copysign(z, y)) + ang::cardinal(n);
146  }
147  }
148 
150  omg = omegashift(omg, +1);
151  if (k2() == 0 && (signbit(omg.c()) || omg.n() != 0))
152  return Math::NaN();
153  return Math::sq(a()) / b() * Pi(_ex, omg.modang(b() / a()));
154  }
155  Angle Conformal3::omega(real x) const {
156  ang omg = Piinv(_ex, (x * b()) / Math::sq(a())).modang(a() / b());
157  return omegashift(omg, -1);
158  }
159 
161  if (kp2() == 0 && (signbit(bet.c()) || bet.n() != 0))
162  return Math::NaN();
163  return Math::sq(c()) / b() * Pi(_ey, bet.modang(b() / c()));
164  }
165  Angle Conformal3::beta(real y) const {
166  return Piinv(_ey, (y * b()) / Math::sq(c())).modang(c() / b());
167  }
168 
169  void Conformal3::Forward(Angle bet, Angle omg, real& xa, real& ya) const {
170  xa = x(omg); ya = y(bet);
171  }
172  void Conformal3::Reverse(real x, real y, Angle& bet, Angle& omg) const {
173  bet = beta(y); omg = omega(x);
174  }
175  void Conformal3::Forward(Angle bet, Angle omg, real& x, real& y,
176  real& m) const {
177  Forward(bet, omg, x, y); m = 1/invscale(bet, omg);
178  }
179  void Conformal3::Reverse(real x, real y, Angle& bet, Angle& omg,
180  real& m) const {
181  Reverse(x, y, bet, omg); m = 1/invscale(bet, omg);
182  }
183 
184  Ellipsoid3 Conformal3::EquivSphere(real x, real y,
185  EllipticFunction& ellx,
186  EllipticFunction& elly) {
187  // find b, k2, kp2 s.t.
188  // b*K(kp2) = x
189  // b*K(k2) = y
190  // x*K(k2) - y*K(kp2) = 0
191 
192  static const real
193  N = (log(real(4)) - log(Math::pi())) / (Math::pi()/2 - log(real(4))),
194  B = exp(N * Math::pi()/2) - pow(real(4), N);
195  real k2 = 1/real(2);
196  bool swapxy = x < y;
197  int countn = 0, countb = 0;
198  if (swapxy) swap(x, y);
199  do {
200  // Now x >= y, k2 <= 1/2
201  real s = x + y, nx = x/s, ny = y/s;
202  if (nx == ny) break; // k2 = 1/2
203  if (ny == 0) { k2 = 0; break; }
204  // Find initial guess assume K(k2) = pi/2, so K(kp2) = nx/ny * pi/2.
205  // Invert using approximate k(K) given in
206  // https://arxiv.org/abs/2505.17159v4
207  real KK = nx/ny * Math::pi()/2;
208  k2 = 16/pow(exp(N*KK) - B, 2/N);
209  // Alternatively using KK = 1/2*log(16/kp) A+S 17.3.26
210  k2 = fmin(1/real(2), 16*exp(-2*KK)); // Make sure guess is sane
211  static const real logk2min = 2*log(numeric_limits<real>::epsilon());
212  // Solve for log(k2) to preserve relative accuracy for tiny k2.
213  real logk2 = log(k2);
214  if (logk2 > logk2min) {
215  auto ksolve = [nx, ny]
216  (real logk2) -> pair<real, real>
217  {
218  real k2 = exp(logk2), kp2 = 1 - k2;
219  EllipticFunction elly(k2), ellx(kp2, 0, k2, 1);
220  real f = nx * elly.K() - ny * ellx.K(),
221  fp = (nx * (elly.E() - kp2 * elly.K()) +
222  ny * (ellx.E() - k2 * ellx.K())) / (2 * k2 * kp2);
223  return pair<real, real>(f, k2*fp);
224  };
225  logk2 = Trigfun::root(Trigfun::KINV, ksolve, 0, logk2,
226  logk2min, -log(real(2)),
227  1, 1, 1,
228  &countn, &countb);
229  }
230  // otherwise accept the asymptotic result
231  k2 = exp(logk2);
232  } while (false);
233  // b*K(kp2) = x
234  // b*K(k2) = y
235 
236  elly = EllipticFunction(k2);
237  ellx = EllipticFunction(1-k2, 0, k2, 1);
238  real b = y / elly.K();
239  real kp2 = 1 - k2;
240  if (swapxy) {
241  swap(x, y);
242  swap(k2, kp2);
243  swap(ellx, elly);
244  }
245  return Ellipsoid3(b, 0, k2, kp2);
246  }
247  Math::real Conformal3::sphericalscale(real ma, real mb) const {
248  real m;
249  if (ma == 0 || mb == 0) {
250  // Let bet = pi/2 - db, omg = pi - do
251  // bet' = pi/2 - c/b*db, omg' = pi/2 - a/b*do
252  // Pi(pi/2 - d, a2, k2) = Pi(a2, k2) - d/(ap2*kp)
253  // x = x0 - (a^2/b) * (a/b*do) / ((1+e2*kp2)*k*sqrt(1+e2*kp2))
254  // = x0 - (a^2/b) * (a/b*do) / (a^2/b^2*k*a/b)
255  // = x0 - b * do / k
256  // y = y0 - (c^2/b) * (c/b*db) / ((1-e2*k2)*kp*sqrt(1-e2*k2))
257  // = y0 - (c^2/b) * (c/b*db) / (c^2/b^2*kp*c/b)
258  // = y0 - b * db / kp
259  // for trixial -> plane
260  // mtp = 1/sqrt(k2*db^2 + kp2*do^2)
261  // on equivalent sphere
262  // x = x0 - b * do / k = x0 - bs * dos / ks
263  // y = y0 - b * db / kp = y0 - bs * dbs / kps
264  // implies
265  // dos = b/bs * ks/k * do
266  // dbs = b/bs * kps/kp * db
267  // for plane -> sphere
268  // mps = sqrt(k2s*dbs^2 + kp2s*dos^2)
269  // = b/bs * sqrt(k2s*kp2s/kp2 * db^2 + kp2s*k2s/k2 * do^2)
270  // = ks*kps/(k*kp) b/bs * sqrt(k2*db^2 + kp2*do^2)
271  // Product of scales = ks*kps/(k*kp) * b/bs
272 
273  // Oblate case see Aux Lat paper Eqs (50) + (51)
274  // sig(1) = sinh(e*atanh(e)) = sg
275  // tan(chi) = tan(phi)*sqrt(1+sg^2) - sg*tan(phi)
276  // = tan(phi) * (sqrt(1+sg^2) - sg)
277  // = tan(phi) / (sqrt(1+sg^2) + sg)
278  // cos(chi) = cos(phi) * (sqrt(1+sg^2) + sg)
279  // Triaxial -> plane scale beta = pi/2 - db
280  // mtp = 1/db
281  // beta' = pi/2 - c/b*db = phi, chi = betas
282  // cos(phi) = c/b * db
283  // cos(chi) = c/b * (sqrt(1+sg^2) + sg) * db
284  // mps = cos(chi) = c/b * (sqrt(1+sg^2) + sg) * db
285  // Product of scales = c/b * (sqrt(1+sg^2) + sg)
286 
287  // Prolate case, replace sg = sinh(-e*atan(e)); c/b -> a/b
288  // sg = sinh(e*atan(e))
289  // Product of scales = a/b / (sqrt(1+sg^2) + sg)
290  if (kp2() == 0) {
291  // oblate pole
292  real e = sqrt(e2()), sg = sinh(e * atanh(e));
293  m = (c()/b()) * (hypot(sg, real(1)) + sg);
294  } else if (k2() == 0) {
295  // prolate pole
296  real e = sqrt(e2()), sg = sinh(e * atan(e));
297  m = (a()/b()) / (hypot(sg, real(1)) + sg);
298  } else {
299  // trixial umbilical
300  m = sqrt(_s.k2() * _s.kp2()/(k2() * kp2())) * (b()/_s.b());
301  }
302  }
303  else
304  m = mb/ma;
305  return m;
306  }
307 
309  vec3& r, vec3& v, real& m) const {
310  real x, y;
311  Forward(bet, omg, x, y, m);
312  real ma = invscale(bet, omg);
313  ang omgs = omegashift(Finv(_exs, x/_s.b()), -1),
314  bets = Finv(_eys, y/_s.b()),
315  alp{}; // alp = 0, due North
316  real mb = sqrt(_s.k2 () * Math::sq(bets.c()) +
317  _s.kp2() * Math::sq(omgs.s()));
318  m = sphericalscale(ma, mb);
319  _s.elliptocart2(bets, omgs, alp, r, v);
320  }
321 
323  Angle& gam, real& m) const {
324  ang bets, omgs, alp;
325  _s.cart2toellip(r, v, bets, omgs, alp);
326  Ellipsoid3::AngNorm(bets, omgs, alp, _s.k2() == 0);
327  gam = -alp;
328  real x = _s.b() * F(_exs, omegashift(omgs, +1)),
329  y = _s.b() * F(_eys, bets);
330  real mb = sqrt(_s.k2() * Math::sq(bets.c()) +
331  _s.kp2() * Math::sq(omgs.s()));
332  omg = omega(x);
333  bet = beta(y);
334  real ma = invscale(bet, omg);
335  m = sphericalscale(ma, mb);
336  }
337 
338  void Conformal3::ForwardOther(const Conformal3& alt, Angle bet, Angle omg,
339  Angle& betalt, Angle& omgalt,
340  Angle& gam, real& m) const {
341  vec3 r, v;
342  real ma, mb;
343  ForwardSphere(bet, omg, r, v, ma);
344  real f = alt._s.b()/_s.b();
345  r[0] *= f; r[1] *= f; r[2] *= f;
346  alt.ReverseSphere(r, v, betalt, omgalt, gam, mb);
347  m = (ma/mb) * f;
348  }
349 
351  Angle betalt, Angle omgalt,
352  Angle& bet, Angle& omg,
353  Angle& gam, real& m) const {
354  alt.ForwardOther(*this, betalt, omgalt, bet, omg, gam, m);
355  m = 1/m; gam = -gam;
356  }
357 
358  } // namespace Triaxial
359 } // namespace GeographicLib
void elliptocart2(Angle bet, Angle omg, vec3 &R) const
Definition: Ellipsoid3.cpp:176
Header for GeographicLib::Trigfun class.
static T pi()
Definition: Math.hpp:187
Math::real x(Angle omg) const
Definition: Conformal3.cpp:149
void Reverse(real x, real y, Angle &bet, Angle &omg) const
Definition: Conformal3.cpp:172
void ForwardOther(const Conformal3 &alt, Angle bet, Angle omg, Angle &betalt, Angle &omgalt, Angle &gam, real &m) const
Definition: Conformal3.cpp:338
Mathematical functions needed by GeographicLib.
Definition: Math.hpp:82
AngleT< Math::real > Angle
Definition: Angle.hpp:760
Elliptic integrals and functions.
Math::real y(Angle bet) const
Definition: Conformal3.cpp:160
Jacobi&#39;s conformal projection of a triaxial ellipsoid.
Definition: Conformal3.hpp:67
static AngleT cardinal(Math::real q)
Definition: Angle.hpp:721
Conformal3(const Ellipsoid3 &t=Ellipsoid3{})
Definition: Conformal3.cpp:20
void cart2toellip(vec3 R, Angle &bet, Angle &omg) const
Definition: Ellipsoid3.cpp:112
Math::real radians() const
Definition: Angle.hpp:542
Header for GeographicLib::Triaxial::Conformal3 class.
static T sq(T x)
Definition: Math.hpp:209
static bool AngNorm(Angle &bet, Angle &omg, Angle &alp, bool alt=false)
Definition: Ellipsoid3.hpp:247
void ReverseSphere(vec3 r, vec3 v, Angle &bet, Angle &omg, Angle &gam, real &m) const
Definition: Conformal3.cpp:322
Namespace for GeographicLib.
Definition: Accumulator.cpp:12
static T NaN()
Definition: Math.cpp:301
void Forward(Angle bet, Angle omg, real &x, real &y) const
Definition: Conformal3.cpp:169
GeographicLib::Math::real real
Definition: Geod3Solve.cpp:25
void swap(GeographicLib::NearestNeighbor< dist_t, pos_t, distfun_t > &a, GeographicLib::NearestNeighbor< dist_t, pos_t, distfun_t > &b)
void ReverseOther(const Conformal3 &alt, Angle betalt, Angle omgalt, Angle &bet, Angle &omg, Angle &gam, real &m) const
Definition: Conformal3.cpp:350
AngleT modang(T m) const
Definition: Angle.hpp:711
Math::real Delta(real sn, real cn) const
void ForwardSphere(Angle bet, Angle omg, vec3 &r, vec3 &v, real &m) const
Definition: Conformal3.cpp:308