GeographicLib 2.6
Loading...
Searching...
No Matches
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
15namespace 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 }
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 }
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
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
Header for GeographicLib::Triaxial::Conformal3 class.
GeographicLib::Angle ang
GeographicLib::Math::real real
Header for GeographicLib::Trigfun class.
static AngleT cardinal(Math::real q)
AngleT modang(T m) const
Definition Angle.hpp:711
Elliptic integrals and functions.
Math::real Delta(real sn, real cn) const
Mathematical functions needed by GeographicLib.
Definition Math.hpp:82
static T sq(T x)
Definition Math.hpp:209
static T pi()
Definition Math.hpp:187
static T NaN()
Definition Math.cpp:301
void ReverseSphere(vec3 r, vec3 v, Angle &bet, Angle &omg, Angle &gam, real &m) const
Math::real x(Angle omg) const
Math::real y(Angle bet) const
void Forward(Angle bet, Angle omg, real &x, real &y) const
void ForwardOther(const Conformal3 &alt, Angle bet, Angle omg, Angle &betalt, Angle &omgalt, Angle &gam, real &m) const
void Reverse(real x, real y, Angle &bet, Angle &omg) const
void ReverseOther(const Conformal3 &alt, Angle betalt, Angle omgalt, Angle &bet, Angle &omg, Angle &gam, real &m) const
void ForwardSphere(Angle bet, Angle omg, vec3 &r, vec3 &v, real &m) const
Conformal3(const Ellipsoid3 &t=Ellipsoid3{})
static bool AngNorm(Angle &bet, Angle &omg, Angle &alp, bool alt=false)
Namespace for operations on triaxial ellipsoids.
Namespace for GeographicLib.
AngleT< Math::real > Angle
Definition Angle.hpp:760
void swap(GeographicLib::NearestNeighbor< dist_t, pos_t, distfun_t > &a, GeographicLib::NearestNeighbor< dist_t, pos_t, distfun_t > &b)