GeographicLib 2.6
Loading...
Searching...
No Matches
GeodesicLine3.cpp
Go to the documentation of this file.
1/**
2 * \file GeodesicLine3.cpp
3 * \brief Implementation for GeographicLib::Triaxial::GeodesicLine3 class
4 *
5 * Copyright (c) Charles Karney (2024-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>
12// Needed by zsetsdiag
13#include <sstream>
15
16namespace GeographicLib {
17 namespace Triaxial {
18
19 using namespace std;
20
21 GeodesicLine3::GeodesicLine3(fline f, fline::fics fic,
22 gline g, gline::gics gic)
23 : _tg(f.tg())
24 , _f(f)
25 , _fic(fic)
26 , _g(g)
27 , _gic(gic)
28 {}
29
30 GeodesicLine3::GeodesicLine3(const Geodesic3& tg)
31 : _f(tg, Geodesic3::gamblk(tg, (tg._umbalt &&
32 tg.kp2() > 0) || tg.k2() == 0))
33 {}
34
35 GeodesicLine3::GeodesicLine3(const Geodesic3& tg,
36 Angle bet1, Angle omg1, Angle alp1)
37 : _tg(tg)
38 {
39 bet1.round();
40 omg1.round();
41 alp1.round();
42 Geodesic3::gamblk gam = _tg.gamma(bet1, omg1, alp1);
43 _f = fline(tg, gam);
44 _fic = fline::fics(_f, bet1, omg1, alp1);
45 _g = gline(tg, gam);
46 _gic = gline::gics(_g, _fic);
47 }
48
49 GeodesicLine3::GeodesicLine3(const Geodesic3& tg,
50 real bet1, real omg1, real alp1)
51 : GeodesicLine3(tg, ang(bet1), ang(omg1), ang(alp1))
52 {}
53
54 void GeodesicLine3::pos1(Angle& bet1, Angle& omg1, Angle& alp1) const {
55 _fic.pos1(_f.transpolar(), bet1, omg1, alp1);
56 }
57
58 void GeodesicLine3::pos1(real& bet1, real& omg1, real& alp1,
59 bool unroll) const {
60 ang bet1a, omg1a, alp1a;
61 pos1(bet1a, omg1a, alp1a);
62 if (!unroll) {
63 (void) Ellipsoid3::AngNorm(bet1a, omg1a, alp1a);
64 bet1a.setn(); omg1a.setn(); alp1a.setn();
65 }
66 bet1 = real(bet1a);
67 omg1 = real(omg1a);
68 alp1 = real(alp1a);
69 }
70
72 Angle& bet2a, Angle& omg2a, Angle& alp2a) const {
73 // Compute points at distance s12
74 real sig2 = _gic.sig1 + s12/_tg.b();
75 ang &phi2a = bet2a, &tht2a = omg2a;
76 int *countn = nullptr, *countb = nullptr;
77 if (_f.gammax() > 0) {
78 real u2, v2;
79 if constexpr (false && biaxspecial(_tg, _g.gammax())) {
80 u2 = gpsi().inv(sig2, countn, countb);
81 v2 = ftht().inv(fpsi()(u2) - _fic.delta, countn, countb);
82 } else
83 // The general triaxial machinery. This is used for non-meridional
84 // geodesics on biaxial ellipsoids.
85 solve2(_fic.delta, sig2, fpsi(), ftht(), gpsi(), gtht(), u2, v2,
86 countn, countb);
87 tht2a = ang::radians(ftht().rev(v2));
88 ang psi2 = ang::radians(fpsi().rev(u2));
89 // Already normalized
90 phi2a = ang(_f.gm().nup * psi2.s(),
91 _fic.phi0.c() * hypot(psi2.c(), _f.gm().nu * psi2.s()),
92 0, true).rebase(_fic.phi0);
93 alp2a = ang(_fic.Ex * hypot(_f.kx() * _f.gm().nu, _f.kxp() * tht2a.c()),
94 _fic.phi0.c() * _f.kx() * _f.gm().nup * psi2.c());
95 } else if (_f.gammax() == 0) {
96 pair<real, real> sig2n = remx(sig2, 2*_g.s0); // reduce to [-s0, s0)
97 real u2, v2;
98 ang psi2;
99 if (_f.kxp2() == 0) {
100 // meridr
101 // ftht = tht
102 // gtht = 0
103 // meridr
104 // fpsi = 0
105 // gpsi = int(sqrt(1-eps*cos(psi)^2), psi)
106 solve2(_fic.delta, sig2n.first,
107 fpsi(), ftht(), gpsi(), gtht(), u2, v2,
108 countn, countb);
109 phi2a = ang::radians(u2);
110 // u2 in in [-pi/2, pi/2]. With long doubles cos(pi/2) < 0 which leads
111 // to a switch in sheets in ellipsoidal coordinates. Fix by setting
112 // cos(phi2a) = +eps
113 if (signbit(phi2a.c())) // Not triggered with doubles and quads
114 phi2a = ang(copysign(real(1), phi2a.s()),
115 numeric_limits<real>::epsilon()/(1<<11), 0, true);
116 psi2 = phi2a + ang::cardinal(2 * sig2n.second);
117 int parity = fmod(sig2n.second, real(2)) != 0 ? -1 : 1;
118 int Ny = _fic.Nx * parity;
119 phi2a = phi2a.reflect(signbit(_fic.phi0.c() * Ny),
120 signbit(_fic.phi0.c())).rebase(_fic.phi0);
121 tht2a = ang::radians(- _fic.delta) + ang::cardinal(2 * sig2n.second);
122 alp2a = ang(_fic.Ex * real(0), _fic.Nx * parity, 0, true);
123 } else {
124 if (sig2n.first - _g.s0 >= -5 * numeric_limits<real>::epsilon()) {
125 sig2n.first = -_g.s0;
126 ++sig2n.second;
127 }
128 real deltax = bigclamp(_fic.delta + sig2n.second * _f.deltashift(), 1);
129 solve2u(deltax, sig2n.first, fpsi(), ftht(), gpsi(), gtht(), u2, v2,
130 countn, countb);
131 // phi2 = fpsi().rev(u2); tht2 = ftht().rev(v2);
132 phi2a = anglam(u2, _f.kxp());
133 psi2 = phi2a + ang::cardinal(2 * sig2n.second);
134 tht2a = anglam(v2, _f.kx());
135 int parity = fmod(sig2n.second, real(2)) != 0 ? -1 : 1;
136 // if_tg.t().kp2 == 0 then meridional oblate
137 int Ny = _fic.Nx * parity;
138 tht2a += ang::cardinal(2 * sig2n.second);
139 tht2a = tht2a + _fic.tht0;
140 phi2a = phi2a.reflect(signbit(_fic.phi0.c() * Ny),
141 signbit(_fic.phi0.c())).rebase(_fic.phi0);
142 // replace cos(phi)/cos(tht) by sech(u)/sech(v)
143 alp2a = ang(_fic.Ex * _f.kxp() / mcosh(v2, _f.kx()),
144 _f.kx() * Ny / mcosh(u2, _f.kxp()));
145 }
146 } else {
147 // gamma = NaN
148 }
149 phi2a.round();
150 tht2a.round();
151 alp2a.round();
152 tht2a = tht2a.flipsign(_fic.Ex);
153 if (_f.transpolar()) {
154 swap(bet2a, omg2a);
155 alp2a.reflect(false, false, true);
156 }
157 alp2a = alp2a.rebase(_fic.alp0);
158 omg2a += ang::cardinal(1);
159 }
160
162 real& bet2, real& omg2, real& alp2,
163 bool unroll) const {
164 ang bet2a, omg2a, alp2a;
165 Position(s12, bet2a, omg2a, alp2a);
166 if (!unroll) {
167 (void) Ellipsoid3::AngNorm(bet2a, omg2a, alp2a);
168 bet2a.setn(); omg2a.setn(); alp2a.setn();
169 }
170 bet2 = real(bet2a);
171 omg2 = real(omg2a);
172 alp2 = real(alp2a);
173 }
174
175 void GeodesicLine3::solve2(real f0, real g0,
176 const hfun& fx, const hfun& fy,
177 const hfun& gx, const hfun& gy,
178 real& x, real& y,
179 int* countn, int* countb) {
180 // Return x and y, s.t.
181 // fx(x) - fy(y) = f0
182 // gx(x) + gy(y) = g0
183 //
184 // fx(x) = fxs*x +/- fxm,
185 // fy(y) = fys*y +/- fym,
186 // gx(x) = gxs*x +/- gxm,
187 // gy(y) = gys*y +/- gym;
188 const bool check = false;
189 real fxm = fx.MaxPlus(), fym = fy.MaxPlus(),
190 gxm = gx.MaxPlus(), gym = gy.MaxPlus(),
191 fxs = fx.Slope(), fys = fy.Slope(), gxs = gx.Slope(), gys = gy.Slope(),
192 // solve
193 // x = ( fys*g0 + gys*f0 ) / den +/- Dx
194 // y = ( fxs*g0 - gxs*f0 ) / den +/- Dy
195 // where
196 den = fxs * gys + fys * gxs, // den > 0
197 qf = fxm + fym, qg = gxm + gym,
198 Dx = (qf * gys + qg * fys) / den,
199 Dy = (qf * gxs + qg * fxs) / den,
200 x0 = (fys * g0 + gys * f0) / den, // Initial guess
201 y0 = (fxs * g0 - gxs * f0) / den,
202 xp = x0 + Dx, xm = x0 - Dx,
203 yp = y0 + Dy, ym = y0 - Dy,
204 mm = 0;
205 if constexpr (check) {
206 real DxA = (-qf * gys + qg * fys) / den,
207 DyA = (-qf * gxs + qg * fxs) / den;
208 real
209 fA = (fx(x0 - Dx) - fy(y0 - DyA)) - f0,
210 gA = (gx(x0 - Dx) + gy(y0 - DyA)) - g0,
211 fB = (fx(x0 - DxA) - fy(y0 - Dy)) - f0,
212 gB = (gx(x0 - DxA) + gy(y0 - Dy)) - g0,
213 fC = (fx(x0 + Dx) - fy(y0 + DyA)) - f0,
214 gC = (gx(x0 + Dx) + gy(y0 + DyA)) - g0,
215 fD = (fx(x0 + DxA) - fy(y0 + Dy)) - f0,
216 gD = (gx(x0 + DxA) + gy(y0 + Dy)) - g0;
217 if (!( fabs(DxA) <= Dx && fabs(DyA) <= Dy ))
218 throw GeographicLib::GeographicErr("Bad Dx/Dy");
219 if (!( fA <= 0 && gA <= 0 )) {
220 cout << scientific << "midA " << fA << " " << gA << "\n";
221 cout << "DA " << Dx << " " << DxA << " " << Dy << " " << DyA << "\n";
223 ("Bad initial midpoints A GeodesicLine3::newt2");
224 }
225 if (!( fB >= 0 && gB <= 0 )) {
226 cout << scientific << "midB " << fB << " " << gB << "\n";
228 ("Bad initial midpoints B GeodesicLine3::newt2");
229 }
230 if (!( fC >= 0 && gC >= 0 )) {
231 cout << scientific << "midC " << fC << " " << gC << "\n";
233 ("Bad initial midpoints C GeodesicLine3::newt2");
234 }
235 if (!( fD <= 0 && gD >= 0 )) {
236 cout << scientific << "midD " << fD << " " << gD << "\n";
237 throw GeographicLib::GeographicErr
238 ("Bad initial midpoints D GeodesicLine3::newt2");
239 }
240 }
241 newt2(f0, g0, fx, fy, gx, gy,
242 xm-mm*Dx, xp+mm*Dx, fx.HalfPeriod(),
243 ym-mm*Dy, yp+mm*Dy, fy.HalfPeriod(),
244 (fx.HalfPeriod() * fxs + fy.HalfPeriod() * fys) / 2,
245 (gx.HalfPeriod() * gxs + gy.HalfPeriod() * gys) / 2,
246 x, y, countn, countb);
247 }
248
249 void GeodesicLine3::solve2u(real d0, real s0,
250 const hfun& fx, const hfun& fy,
251 const hfun& gx, const hfun& gy,
252 real& u, real& v,
253 int* countn, int* countb) {
254 // Return u and v, s.t.
255 // fx(u) - fy(v) = d0
256 // gx(u) + gy(v) = s0
257 // specialized for umbilics
258 //
259 // fx, fy, gx, gy are increasing functions defined in [-1, 1]*pi2
260 // Assume fx(0) = fy(0) = gx(0) = gy(0) = 0
261 real pi2 = Geodesic3::BigValue(),
262 sbet = gx.Max(), somg = gy.Max(), stot = sbet + somg,
263 dbet = fx.Max(), domg = fy.Max(), del = dbet - domg;
264 if (fabs(s0) - stot >= -5 * numeric_limits<real>::epsilon()) {
265 // close to umbilic points we have
266 // fx(u) = u -/+ dbet
267 // fy(v) = v -/+ domg
268 // fx(u) - fy(v) = d0
269 // constrain u+v = +/- 2 * pi2
270 // u = +/- pi2 + (d0 +/- (dbet - domg))/2
271 // v = +/- pi2 - (d0 +/- (dbet - domg))/2
272 real t0 = copysign(pi2, s0),
273 t1 = (d0 + (1 - 2 * signbit(s0)) * del) / 2;
274 u = t0 + t1; v = t0 - t1;
275 } else if (fabs(d0) > 2*pi2/3 &&
276 fabs((1 - 2 * signbit(d0)) * s0 - (sbet - somg)) <=
277 7 * numeric_limits<real>::epsilon()) {
278 if (d0 > 0) {
279 u = 2*d0/3; v = -1*d0/3;
280 } else {
281 u = 1*d0/3; v = -2*d0/3;
282 }
283 } else {
284 real mm = 2;
285 newt2(d0, s0, fx, fy, gx, gy,
286 -mm * pi2, mm * pi2, pi2,
287 -mm * pi2, mm * pi2, pi2,
288 pi2, pi2, u, v, countn, countb);
289 }
290 }
291
292 void GeodesicLine3::newt2(real f0, real g0,
293 const hfun& fx, const hfun& fy,
294 const hfun& gx, const hfun& gy,
295 real xa, real xb, real xscale,
296 real ya, real yb, real yscale,
297 real fscale, real gscale,
298 real& x, real& y,
299 int* countn, int* countb) {
300 // solve
301 // f = fx(x) - fy(y) - f0 = 0
302 // g = gx(x) + gy(y) - g0 = 0
303 // for x, y. Assume that
304 // fx and fy are increasing functions
305 // gx and gy are non-decreasing functions
306 // The solution is bracketed by x in [xa, xb], y in [ya, yb]
307 static const real tol = numeric_limits<real>::epsilon();
308 const bool debug = Geodesic3::debug_, check = false;
309 // Relax [fg]tol to /10 instead of /100. Otherwise solution resorts to
310 // too much bisection. Example:
311 // echo 63 -173 -61.97921997838416712 -4.64409746197940890408 |
312 // ./Geod3Solve $PROX
313 const real
314 ftol = tol * fscale/10,
315 gtol = tol * gscale/10,
316 xtol = pow(tol, real(0.75)) * xscale,
317 ytol = pow(tol, real(0.75)) * yscale,
318 // If tolmult == 0, umbilical case
319 // echo 70 0 180 0.46138515584816834054 | ./Geod3Solve $SET
320 // take countn/countb = 55/54 iterations to converge. With tolmult == 1,
321 // it needs 5/3 iterations.
322 tolmult = 1;
323 int cntn = 0, cntb = 0;
324 real oldf = Math::infinity(), oldg = oldf, olddx = oldf, olddy = oldf;
325 zset xset(zvals(xa, fx(xa), gx(xa)),
326 zvals(xb, fx(xb), gx(xb))),
327 yset(zvals(ya, fy(ya), gy(ya)),
328 zvals(yb, fy(yb), gy(yb)));
329 // x = xset.bisect(), y = yset.bisect();
330 auto p = zsetsbisect(xset, yset, f0, g0, false);
331 x = p.first; y = p.second;
332 if constexpr (check) {
333 // A necessary condition for a root is
334 // f01 <= 0 <= f10
335 // g00 <= 0 <= g11
336 real
337 f01 = (xset.min().fz - yset.max().fz) - f0,
338 f10 = (xset.max().fz - yset.min().fz) - f0,
339 g00 = (xset.min().gz + yset.min().gz) - g0,
340 g11 = (xset.max().gz + yset.max().gz) - g0;
341 // Allow equality on the initial points
342 if (!( f01 <= 0 && 0 <= f10 && g00 <= 0 && 0 <= g11 ))
343 throw GeographicLib::GeographicErr
344 ("Bad initial points GeodesicLine3::newt2");
345 zvals xv(x, fx(x), gx(x)), yv(y, fy(y), gy(y));
346 }
347 // degen is a flag detected degeneracy to a 1d system. Only the case gy(y)
348 // = const is treated. Then g = gx(x) + gy(y) - g0 = 0 converges as a 1d
349 // Newton system which fixes the value of x. Once x is found (detected by
350 // xset.min().z and xset.max().z being consecutive numbers), g0 is adjusted
351 // to that the g equation is satisfied exactly. This allows f = fx(x) -
352 // fy(y) - f0 = 0 to be solved reliably for y.
353 bool bis = false, degen = false;
354 int ibis = -1, i = 0;
355 for (; i < maxit_ ||
356 (Geodesic3::throw_ && (throw GeographicLib::GeographicErr
357 ("Convergence failure GeodesicLine3::newt2"),
358 false));
359 ++i) {
360 ++cntn;
361 if (!degen && nextafter(xset.min().z, xset.max().z) == xset.max().z &&
362 yset.min().gz == yset.max().gz) {
363 degen = true;
364 real ga = (xset.min().gz + yset.min().gz) - g0,
365 gb = (xset.max().gz + yset.max().gz) - g0;
366 x = gb < -ga ? xset.max().z : xset.min().z;
367 // Adjust g0 so that g = 0 and dx = 0
368 if (gb < -ga) {
369 g0 = xset.max().gz + yset.max().gz;
370 zvals xx = xset.max();
371 xset.insert(xx, -1);
372 } else {
373 g0 = xset.min().gz + yset.min().gz;
374 zvals xx = xset.min();
375 xset.insert(xx, +1);
376 }
377 }
378 zvals xv(x, fx(x), gx(x)), yv(y, fy(y), gy(y));
379 // zsetsinsert updates xv and yv to enforce monotonicity of f and g
380 zsetsinsert(xset, yset, xv, yv, f0, g0);
381 real f = (xv.fz - yv.fz) - f0, g = (xv.gz + yv.gz) - g0;
382 if ((fabs(f) <= ftol && fabs(g) <= gtol) || isnan(f) || isnan(g)) {
383 if constexpr (debug)
384 cout << "break0 " << scientific << f << " " << g << "\n";
385 break;
386 }
387 real
388 fxp = fx.deriv(x), fyp = fy.deriv(y),
389 gxp = gx.deriv(x), gyp = gy.deriv(y),
390 den = fxp * gyp + fyp * gxp,
391 dx = -( gyp * f + fyp * g) / den, // if degen, dx = 0
392 dy = -(-gxp * f + fxp * g) / den, // if degen dy = f / fyp
393 xn = x + dx, yn = y + dy,
394 dxa = 0, dya = 0;
395 xa = xset.min().z; xb = xset.max().z;
396 ya = yset.min().z; yb = yset.max().z;
397 if constexpr (debug) {
398 bool bb = gyp == 0 &&
399 nextafter(xset.min().z, xset.max().z) == xset.max().z;
400 cout << "DERIV " << i << " " << fxp << " " << fyp << " " << gxp << " " << gyp << " " << bb << "\n";
401 cout << "DY " << i << " " << -gxp * f << " " << fxp * g << "\n";
402 cout << "FG " << i << " " << f << " " << g << "\n";
403 cout << "DXY " << i << " " << degen << " " << dx << " " << dy << "\n";
404 }
405 if constexpr (check) {
406 if (!( fxp >= 0 && fyp >= 0 && gxp >= 0 && gyp >= 0 && den > 0 )) {
407 cout << "DERIVS " << x << " " << y << " "
408 << fxp << " " << fyp << " "
409 << gxp << " " << gyp << " " << den << "\n";
410 throw GeographicLib::GeographicErr
411 ("Bad derivatives GeodesicLine3::newt2");
412 }
413 }
414 bool cond1 = den > 0 &&
415 (i < ibis + 2 ||
416 ((2*fabs(f) < oldf || 2*fabs(g) < oldg) ||
417 (2*fabs(dx) < olddx || 2*fabs(dy) < olddy))),
418 cond2 = xn >= xa-xtol*tolmult && xn <= xb+xtol*tolmult &&
419 yn >= ya-ytol*tolmult && yn <= yb+ytol*tolmult;
420 if (cond1 && cond2) {
421 oldf = fabs(f); oldg = fabs(g); olddx = fabs(dx); olddy = fabs(dy);
422 x = xn; y = yn;
423 bis = false;
424 if (!(fabs(dx) > xtol || fabs(dy) > ytol)) {
425 if constexpr (debug)
426 cout << "break1 " << scientific << dx << " " << dy << " "
427 << f << " " << g << "\n";
428 break;
429 }
430 } else {
431 // xn = xset.bisect(); yn = yset.bisect();
432 p = zsetsbisect(xset, yset, f0, g0, false);
433 xn = p.first; yn = p.second;
434 ++cntb;
435 if (x == xn && y == yn) {
436 if constexpr (debug)
437 cout << "break2 " << f << " " << g << "\n";
438 break;
439 }
440 dxa = xn - x; dya = yn - y;
441 x = xn; y = yn;
442 bis = true;
443 ibis = i;
444 }
445 (void) bis;
446 if constexpr (debug)
447 cout << "AA " << scientific << setprecision(4)
448 << x-xa << " " << xb-x << " "
449 << y-ya << " " << yb-y << "\n";
450 if constexpr (debug)
451 cout << "CC " << i << " "
452 << bis << " " << cond1 << " " << cond2 << " "
453 << scientific << setprecision(2) << f << " " << g << " "
454 << dx << " " << dy << " " << dxa << " " << dya << " "
455 << xb-xa << " " << yb-ya << "\n";
456 if constexpr (debug)
457 cout << "BOX " << i << " " << bis << " "
458 << xset.num() << " " << yset.num() << " "
459 << scientific << setprecision(3)
460 << xset.max().z - xset.min().z << " "
461 << yset.max().z - yset.min().z << "\n";
462 }
463 if (countn)
464 *countn += cntn;
465 if (countb)
466 *countb += cntb;
467 if constexpr (debug) {
468 cout << "CNT " << cntn << " " << cntb << "\n";
469 cout << "XY " << setprecision(18) << x << " " << y << "\n";
470 }
471 }
472
473 int GeodesicLine3::zset::insert(zvals& t, int flag) {
474 // Inset t into list. flag = -/+ 1 indicates new min/max.
475 // Return -1 if t was already present; othersize return index of newly
476 // inserted value.
477 // If monotonic, value of t.fz and t.gz is adjusted to ensuire monotonicity:
478 // if a.z == b.z then a.fz == b.gz && a.fz == b.gz
479 // if a.z < b.z then a.fz <= b.gz && a.fz <= b.gz
480 using std::isnan;
481 // Need this flag to be set otherwise conditions for setting the bounds on
482 // the allowed results are not met and newt2 fails to converge.
483 const bool monotonic = true;
484 int ind = -1;
485 if (isnan(t.z)) return ind;
486 if (t < min()) {
487 if constexpr (monotonic) {
488 t.fz = fmin(t.fz, min().fz);
489 t.gz = fmin(t.gz, min().gz);
490 }
491 } else if (t == min()) {
492 // Check if t is "other" endpoint and collapse bracket to zero
493 if (flag > 0) _s.resize(1);
494 t = min();
495 } else if (t == max()) {
496 // Check if t is "other" endpoint and collapse bracket to zero
497 if (flag < 0) { _s[0] = _s.back(); _s.resize(1); }
498 t = max();
499 } else if (max() < t) {
500 if constexpr (monotonic) {
501 t.fz = fmax(t.fz, max().fz);
502 t.gz = fmax(t.gz, max().gz);
503 }
504 }
505 if (!(min() < t && t < max())) // Not in range
506 return ind;
507 // Now min() < t < max()
508 auto p = std::lower_bound(_s.begin(), _s.end(), t);
509 if (p == _s.end()) return ind; // Can't happen
510 // Fix components of t
511 bool ins = !(*p == t);
512 if constexpr (monotonic) {
513 if (ins) {
514 t.fz = Math::clamp(t.fz, (p-1)->fz, p->fz);
515 t.gz = Math::clamp(t.gz, (p-1)->gz, p->gz);
516 } else // z components match
517 t = *p; // set fz and gz values
518 }
519 if (flag < 0) {
520 _s.erase(_s.begin(), p);
521 if (ins) {
522 _s.insert(_s.begin(), t);
523 ind = 0;
524 }
525 } else if (flag > 0) {
526 if (ins) {
527 _s.erase(p, _s.end());
528 _s.push_back(t);
529 ind = int(_s.size()) - 1;
530 } else
531 _s.erase(p+1, _s.end());
532 } else if (ins) {
533 ind = int(p - _s.begin());
534 _s.insert(p, t);
535 }
536 // else it's a duplicate and not a new end value
537 return ind;
538 }
539
540 void GeodesicLine3::zsetsinsert(zset& xset, zset& yset,
541 zvals& xfg, zvals& yfg,
542 real f0, real g0) {
543 const bool debug = Geodesic3::debug_;
544 real x0 = 0, y0 = 0;
545 int xind = xset.insert(xfg), yind = yset.insert(yfg);
546 if constexpr (debug) {
547 cout << "BOXA " << xset.num() << " " << yset.num() << " "
548 << xind << " " << yind << " "
549 << xset.min().z - x0 << " " << xset.max().z - x0 << " "
550 << yset.min().z - y0 << " " << yset.max().z - y0 << " "
551 << xset.max().z - xset.min().z << " "
552 << yset.max().z - yset.min().z << "\n";
553 zsetsdiag(xset, yset, f0, g0);
554 }
555 if (xind < 0 && yind < 0) return;
556 // We update two sets of bounds xa[0], xb[0], etc. use the values at the
557 // newly inserted row and column. xa[1], xb[1], etc. use just the value at
558 // the center point.
559 zvals xa[2] = {xset.min(), xset.min()},
560 xb[2] = {xset.max(), xset.max()},
561 ya[2] = {yset.min(), yset.min()},
562 yb[2] = {yset.max(), yset.max()};
563 for (int i = 0; i < xset.num(); ++i) {
564 const zvals& x = xset.val(i);
565 for (int j = 0; j < yset.num(); ++j) {
566 if (i == xind || j == yind) {
567 const zvals& y = yset.val(j);
568 real f = (x.fz - y.fz) - f0, g = (x.gz + y.gz) - g0;
569 // Update bounds as follows
570 //
571 // y=-x y=x
572 // g=0 f=0
573 // \ ind=-2 /
574 // \ f<0 /
575 // \ g>0 /
576 // \ yb /
577 // \ D /
578 // ind=-4 \ / ind=4
579 // f<0 \ / f>0
580 // g<0 X g>0
581 // xa / \ xb
582 // A / \ C
583 // / B \.
584 // / f>0 \.
585 // / g<0 \.
586 // / ya \.
587 // / ind=2 \.
588
589 // If the pattern is
590 //
591 // BOXF --+
592 // BOXF -++
593 // BOXF -++
594 //
595 // BOXG ..+
596 // BOXG ..+
597 // BOXG ---
598 //
599 // The, allowing equality (tight bounds) collapses the set to 1x1
600 // (the middle element). This is clearly wrong since now we can't
601 // get f = 0. We detect this by checking the values of f and g at
602 // the corners:
603 //
604 // f <= 0 at NW corner f >= 0 at SE corner
605 // g <= 0 at SW corner g >= 0 at NE corner
606 //
607 // If theses requirements fail, use bounds updated with just the
608 // center point i == xind && j == yind.
609 if (f <= 0) {
610 if (g <= 0 && xa[0] < x) xa[0] = x;
611 if (g >= 0 && y < yb[0]) yb[0] = y;
612 if (i == xind && j == yind) {
613 if (g <= 0 && xa[1] < x) xa[1] = x;
614 if (g >= 0 && y < yb[1]) yb[1] = y;
615 }
616 }
617 if (f >= 0) {
618 if (g <= 0 && ya[0] < y) ya[0] = y;
619 if (g >= 0 && x < xb[0]) xb[0] = x;
620 if (i == xind && j == yind) {
621 if (g <= 0 && ya[1] < y) ya[1] = y;
622 if (g >= 0 && x < xb[1]) xb[1] = x;
623 }
624 }
625 }
626 }
627 }
628 int k = 0;
629 for (; k < 2; ++k) {
630 // Check signs at corners
631 if ((xa[k].fz - yb[k].fz) - f0 <= 0 &&
632 (xb[k].fz - ya[k].fz) - f0 >= 0 &&
633 (xa[k].gz + ya[k].gz) - g0 <= 0 &&
634 (xb[k].gz + yb[k].gz) - g0 >= 0)
635 // corner constraints met
636 break;
637 }
638 int kk = min(k, 1);
639 if constexpr (debug)
640 cout << "BOXK " << k << " " << xind << " " << yind << "\n";
641 xset.insert(xa[kk], -1);
642 xset.insert(xb[kk], +1);
643 yset.insert(ya[kk], -1);
644 yset.insert(yb[kk], +1);
645 if constexpr (debug) {
646 cout << "BOXB " << xset.num() << " " << yset.num() << " "
647 << xset.min().z - x0 << " " << xset.max().z - x0 << " "
648 << yset.min().z - y0 << " " << yset.max().z - y0 << " "
649 << xa[kk].z - x0 << " " << xb[kk].z - x0 << " "
650 << ya[kk].z - y0 << " " << yb[kk].z - y0 << "\n";
651 zsetsdiag(xset, yset, f0, g0);
652 }
653 if (k == 2)
654 throw GeographicLib::GeographicErr
655 ("Bad corner points GeodesicLine3::zsetsinsert");
656 }
657
658 void GeodesicLine3::zsetsdiag(const zset& xset, const zset& yset,
659 real f0, real g0) {
660 ostringstream fs, gs;
661 for (int j = yset.num() - 1; j >= 0; --j) {
662 const zvals& y = yset.val(j);
663 fs << "BOXF ";
664 gs << "BOXG ";
665 for (int i = 0; i < xset.num(); ++i) {
666 const zvals& x = xset.val(i);
667 real f = (x.fz - y.fz) - f0, g = (x.gz + y.gz) - g0;
668 fs << (f == 0 ? '.' : f < 0 ? '-' : '+');
669 gs << (g == 0 ? '.' : g < 0 ? '-' : '+');
670 }
671 fs << "\n";
672 gs << "\n";
673 }
674 cout << fs.str() << gs.str();
675 }
676
677 pair<Math::real, Math::real>
678 GeodesicLine3::zsetsbisect(const zset& xset, const zset& yset,
679 real f0, real g0, bool secant) {
680 if constexpr (true)
681 return pair<real, real>(xset.bisect(), yset.bisect());
682 else if (secant && xset.num() <= 2 && yset.num() <= 2) {
683 // Use secant solution
684 real
685 dx = xset.max().z - xset.min().z,
686 dy = yset.max().z - yset.min().z,
687 fx1 = (xset.max().fz - xset.min().fz) / (dx == 0 ? 1 : dx),
688 fy1 = (yset.max().fz - yset.min().fz) / (dy == 0 ? 1 : dy),
689 gx1 = (xset.max().gz - xset.min().gz) / (dx == 0 ? 1 : dx),
690 gy1 = (yset.max().gz - yset.min().gz) / (dy == 0 ? 1 : dy),
691 den = fx1*gy1 + fy1*gx1,
692 f00 = (xset.min().fz + yset.min().fz) - f0,
693 g00 = (xset.min().gz + yset.min().gz) - g0,
694 x = -gy1*f00 - fy1*g00,
695 y = gx1*f00 - fx1*g00;
696 if (den <= 0) den = 1;
697 x /= den;
698 y /= den;
699 if (x <= 0 || x >= 1) x = 1/real(2);
700 if (y <= 0 || y >= 1) y = 1/real(2);
701 x = xset.min().z + x * dx;
702 y = yset.min().z + y * dy;
703 return pair<real, real>(x, y);
704 } else {
705 // A necessary condition for a root is
706 // f01 <= 0 <= f10
707 // g00 <= 0 <= g11
708 int cnt = 0;
709 real xgap = -1, ygap = -1, x = Math::NaN(), y = Math::NaN();
710 for (int i = 0; i < max(1, xset.num() - 1); ++i) {
711 int i1 = min(i + 1, xset.num() - 1);
712 real xgap1 = xset.val(i1).z - xset.val(i).z,
713 xmean = (xset.val(i1).z + xset.val(i).z) / 2;
714 for (int j = 0; j < max(1, yset.num() - 1); ++j) {
715 int j1 = min(j + 1, yset.num() - 1);
716 real ygap1 = yset.val(j1).z - yset.val(j).z,
717 ymean = (yset.val(j1).z + yset.val(j).z) / 2;
718 real
719 f01 = (xset.val(i ).fz - yset.val(j1).fz) - f0,
720 f10 = (xset.val(i1).fz - yset.val(j ).fz) - f0,
721 g00 = (xset.val(i ).gz + yset.val(j ).gz) - g0,
722 g11 = (xset.val(i1).gz + yset.val(j1).gz) - g0;
723 if (f01 <= 0 && 0 <= f10 && g00 <= 0 && 0 <= g11) {
724 ++cnt;
725 if (xgap1 > xgap) { xgap = xgap1; x = xmean; }
726 if (ygap1 > ygap) { ygap = ygap1; y = ymean; }
727 }
728 }
729 }
730 if (cnt == 0)
731 throw GeographicLib::GeographicErr
732 ("No legal box GeodesicLine3::zsetsbisect");
733 return pair<real, real>(x, y);
734 }
735 }
736
737 void GeodesicLine3::Hybrid(ang betomg2,
738 ang& bet2a, ang& omg2a, ang& alp2a,
739 real& s12, bool betp) const {
740 fline::disttx d = _f.Hybrid(_fic, betomg2, bet2a, omg2a, alp2a, betp);
741 s12 = _g.dist(_gic, d);
742 }
743
744 GeodesicLine3::fline::disttx
745 GeodesicLine3::fline::Hybrid(const fics& fic, ang betomg2,
746 ang& bet2a, ang& omg2a, ang& alp2a,
747 bool betp) const {
748 // Is the control variable psi or tht?
749 bool psip = !transpolar() ? betp : !betp;
750 if (!betp) betomg2 -= ang::cardinal(1);
751 ang tau12;
752 if (psip) {
753 ang phi2 = betomg2;
754 if (gammax() > 0) {
755 real spsi = phi2.s(),
756 // In evaluating equivalent expressions, choose the one with minimum
757 // cancelation. Need the 0 + x to convert -0 to +0. (Note sqrt(-0) =
758 // -0 and fmax(+0, -0) may be -0.)
759 cpsi = nu() < nup() ?
760 (phi2.c() - nu()) * (phi2.c() + nu()) :
761 (nup() - phi2.s()) * (nup() + phi2.s());
762 // Return nan if geodesic can't reach phi2 -- given by sqrt(neg) -- but
763 // allow a little slop.
764 cpsi = !(cpsi > -numeric_limits<real>::epsilon()) ? Math::NaN() :
765 (signbit(cpsi) ? 0 : sqrt(cpsi));
766 // Need Angle(0, 0) to be treated like Angle(0, 1) here.
767 ang psi12 = (ang(spsi, cpsi) - fic.psi1).base();
768 // convert -180deg to 180deg
769 if (signbit(psi12.s()))
770 psi12 = ang(0, copysign(real(1), psi12.c()), 0, true);
771 tau12 = psi12;
772 } else if (gammax() == 0) {
773 tau12 = (fic.Nx > 0 ? phi2 - fic.phi1 :
774 phi2 + fic.phi1 + ang::cardinal(2)).base();
775 } else
776 tau12 = ang::NaN();
777 } else {
778 ang tht2 = betomg2.flipsign(fic.Ex);
779 if (gammax() >= 0) {
780 // FIX THIS!! betp shouldn't be appearing here.
781 // Test case
782 // echo -88 21 88 -111 | ./Geod3Solve -i $SET --hybridalt
783 ang tht2b = tht2; tht2b.reflect(false, betp && fic.Ex < 0);
784 ang tht12 = tht2b - fic.tht1;
785 // convert -180deg to 180deg
786 if (signbit(tht12.s()))
787 tht12 = ang(0, copysign(real(1), tht12.c()), 0, true);
788 tau12 = tht12;
789 } else
790 tau12 = ang::NaN();
791 }
792 disttx ret = ArcPos0(fic, tau12.base(), bet2a, omg2a, alp2a, betp);
793 return ret;
794 }
795
796 GeodesicLine3::fline::fline(const Geodesic3& tg, bool neg)
797 : _tg(tg)
798 , _gm(tg, neg)
799 {}
800
801 GeodesicLine3::fline::fline(const Geodesic3& tg, Geodesic3::gamblk gam)
802 : _tg(tg)
803 , _gm(gam)
804 , _fpsi(false, _gm.kx2 , _gm.kxp2,
805 +(_gm.transpolar ? -1 : 1) * _tg.e2(),
806 -(_gm.transpolar ? -1 : 1) * _gm.gamma, _tg)
807 , _ftht(false, _gm.kxp2 , _gm.kx2,
808 -(_gm.transpolar ? -1 : 1) * _tg.e2(),
809 +(_gm.transpolar ? -1 : 1) * _gm.gamma, _tg)
810 {
811 // Only needed for umbilical lines
812 _deltashift = _gm.gamma == 0 ?
813 (_tg.k2() > 0 && _tg.kp2() > 0 ? 2 * (_fpsi.Max() - _ftht.Max()) : 0) :
814 Math::NaN();
815 }
816
817 GeodesicLine3::gline::gline(const Geodesic3& tg, bool neg)
818 : _tg(tg)
819 , _gm(tg, neg)
820 {}
821
822 GeodesicLine3::gline::gline(const Geodesic3& tg, const Geodesic3::gamblk& gm)
823 : _tg(tg)
824 , _gm(gm)
825 , _gpsi(true, _gm.kx2 , _gm.kxp2,
826 +(_gm.transpolar ? -1 : 1) * _tg.e2(),
827 -(_gm.transpolar ? -1 : 1) * _gm.gamma, _tg)
828 , _gtht(true, _gm.kxp2 , _gm.kx2,
829 -(_gm.transpolar ? -1 : 1) * _tg.e2(),
830 +(_gm.transpolar ? -1 : 1) * _gm.gamma, _tg)
831 , s0(_gm.gammax == 0 ? _gpsi.Max() + _gtht.Max() : 0)
832 {}
833
834 Math::real GeodesicLine3::fline::Hybrid0(const fics& fic, ang bet2, ang omg2,
835 bool betp) const {
836 ang bet2a, omg2a, alp2a;
837 (void) Hybrid(fic, betp ? bet2 : omg2, bet2a, omg2a, alp2a, betp);
838 bool angnorm = true || betp;
839 if (angnorm)
840 (void) Ellipsoid3::AngNorm(bet2a, omg2a, alp2a, !betp);
841 if (betp) {
842 omg2a -= omg2;
843 return omg2a.radians0();
844 } else {
845 bet2a -= bet2;
846 return angnorm ? bet2a.radians0() : bet2.radians();
847 }
848 }
849
850 GeodesicLine3::fline::disttx
851 GeodesicLine3::fline::ArcPos0(const fics& fic, ang tau12,
852 ang& bet2a, ang& omg2a, ang& alp2a,
853 bool betp) const {
854 // XXX fix for biaxial
855 disttx ret{Math::NaN(), Math::NaN(), 0};
856 bool psip = transpolar() ? !betp : betp;
857 ang &phi2a = bet2a, &tht2a = omg2a;
858 if (gammax() > 0) {
859 ang psi2;
860 real u2, v2, u2x = 0;
861 if (psip) {
862 psi2 = tau12 + fic.psi1;
863 v2 = fpsi().fwd(psi2.radians());
864 u2 = ftht().inv(fpsi()(v2) - fic.delta);
865 tht2a = ang::radians(ftht().rev(u2));
866 } else {
867 tht2a = fic.tht1 + tau12;
868 u2 = ftht().fwd(tht2a.radians());
869 u2x = ftht()(u2) + fic.delta;
870 v2 = fpsi().inv(u2x);
871 psi2 = ang::radians(fpsi().rev(v2));
872 }
873 // Already normalized
874 phi2a = ang(nup() * psi2.s(),
875 fic.phi0.c() * hypot(psi2.c(), nu() * psi2.s()),
876 0, true).rebase(fic.phi0);
877 real s = fic.Ex * hypot(kx() * nu(), kxp() * tht2a.c()),
878 c = fic.phi0.c() * kx() * nup() * psi2.c();
879 if (s == 0 && c == 0)
880 (transpolar() ? s : c) = 1;
881 alp2a = ang(s, c);
882 ret.phiw2 = v2;
883 ret.thtw2 = u2;
884 } else if (gammax() == 0) {
885 real u2, v2;
886 int ii;
887 if (psip) {
888 phi2a = fic.phi1 + tau12.flipsign(fic.Nx);
889 // remainder with result in [-pi/2, pi/2)
890 pair<real, real> phi2n =
891 // remx(fic.Nx * (phi2a - fic.phi0).radians(), Math::pi());
892 remx((phi2a - fic.phi0).flipsign(fic.Nx));
893 u2 = fpsi().fwd(phi2n.first);
894 int parity = fmod(phi2n.second, real(2)) != 0 ? -1 : 1;
895 if (kxp() == 0) {
896 // v2 is independent on u2
897 v2 = 0;
898 tht2a = ang::radians(-fic.Ex * fic.delta) +
899 ang::cardinal(2*fic.Ex*phi2n.second);
900 alp2a = fic.alp1.nearest(2U) + ang::cardinal(parity < 0 ? 2 : 0);
901 } else {
902 real deltax = bigclamp(fic.delta + phi2n.second * _deltashift, 2);
903 v2 = ftht().inv(fpsi()(u2) - deltax);
904 tht2a = ang::radians(parity * ftht().rev(v2))
905 .rebase(fic.tht0);
906 // Conflict XXX
907 // testset -50 180 20 0 want fic.Nx multiplying s()
908 // testspha -20 90 20 -90 wants fic.Nx multiplying c()
909 alp2a = ang(kxp() * fic.Ex * parity / mcosh(v2, kx()),
910 fic.Nx * kx() / mcosh(u2, kxp()));
911 // Move forward from umbilical point
912 phi2a += ang::eps().flipsign(fic.Nx);
913 }
914 ii = int(phi2n.second);
915 } else {
916 tht2a = fic.tht1 + tau12;
917 // remainder with result in [-pi/2, pi/2)
918 pair<real, real> tht2n =
919 // remx(fic.Ex * (tht2a - fic.tht0).radians(), Math::pi());
920 remx(tht2a - fic.tht0);
921 v2 = ftht().fwd(tht2n.first);
922 u2 = 0;
923 int parity = fmod(tht2n.second, real(2)) != 0 ? -1 : 1;
924 if (kxp() == 0) {
925 if (fic.phi1.c() != 0 && tau12 == tau12.nearest(2U)) {
926 // STILL TO DO...
927 // Special case for finding conjugate points on meridional
928 // geodesics, gamma = 0, tau12 = multiple of pi. This is
929 // specialized for prolate ellipsoids for now.
930 real npi = (tau12.ncardinal() + fic.phi1.nearest(2U).ncardinal())
931 * Math::pi()/2;
932 // Don't worry about the case where we start at a pole -- this is
933 // already handled in Geodesic3::Inverse.
934 //
935 // Solve F(tpsi2) = tpsi2 - Deltaf(n*pi + atan(tpsi2))
936 // = (tan(psi1) - Deltaf(psi1)) = c
937 //
938 // for tpsi2, starting guess tpsi2 = tan(psi1).
939 // Test case prolate 1 3 0 1
940 // p1 = [-90, -1]
941 // [sx,a1x,a2x] = t.distance(p1,[90,178.9293750483])
942 // -> a1x = 90
943 // [sx,a1x,a2x] = t.distance(p1,[90,178.9293750484])
944 // -> a1x = 90.0023
945 // omg1 = -91 -> omg2 = 178.9293750483-90 = 88.9293750483
946 real c = fic.Nx * (fic.phi1.t() - fpsi().df(fic.phi1.radians())),
947 l = exp(Geodesic3::BigValue()),
948 tpsi2 = Trigfun::root(Trigfun::ARCPOS0,
949 [this, npi] (real tpsi) -> pair<real, real>
950 {
951 real psi = atan(tpsi);
952 return pair<real, real>
953 (tpsi - fpsi().df(npi + psi),
954 1 - fpsi().dfp(psi) /
955 (1 + Math::sq(tpsi)));
956 },
957 c, fic.psi1.t(), -l, l);
958 v2 = atan(tpsi2);
959 phi2a = (ang(tpsi2, 1) + fic.phi1.nearest(2U))
960 .flipsign(parity*fic.Nx).rebase(fic.phi0);
961 alp2a = ang::cardinal(fic.Nx * parity == 1 ? 0 : 2);
962 } else {
963 u2 = v2 == 0 ? 0 : copysign(Math::pi()/2, tht2n.first);
964 phi2a = ang::cardinal(fabs(v2) == 0
965 ? 0 : copysign(real(1), v2 * fic.Nx))
966 .rebase(fic.phi0);
967 alp2a = fabs(v2) == 0 ? ang::cardinal(2) :
968 fic.alp1.nearest(2U) +
969 ang::cardinal(parity == 1 ? 0 : 2) +
970 ang::radians(v2).flipsign(parity * phi2a.s());
971 }
972 } else {
973 real deltax = bigclamp(fic.delta + tht2n.second * _deltashift, 2);
974 u2 = fpsi().inv(ftht()(v2) + deltax);
975 real phi2 = fic.Nx * parity * fpsi().rev(u2);
976 phi2a = ang::radians(phi2);
977 alp2a = ang(fic.Ex * kxp() / mcosh(v2, kx()),
978 kx() * fic.Nx * parity / mcosh(u2, kxp()));
979 tht2a += ang::eps();
980 }
981 ii = int(tht2n.second);
982 // Move forward from umbilical point
983 }
984 ret.phiw2 = u2;
985 ret.thtw2 = v2;
986 ret.ind2 = ii;
987 } else {
988 // gamma == NaN
989 }
990
991 tht2a = tht2a.flipsign(fic.Ex);
992 if (transpolar()) {
993 swap(bet2a, omg2a);
994 alp2a.reflect(false, false, true);
995 }
996 omg2a += ang::cardinal(1);
997 alp2a = alp2a.rebase(fic.alp0);
998 return ret;
999 }
1000
1001 GeodesicLine3::fline::fics::fics(const fline& f, ang bet10, ang omg10,
1002 ang alp10)
1003 : tht1(omg10 - ang::cardinal(1))
1004 , phi1(bet10)
1005 , alp1(alp10)
1006 {
1007 static const real eps = numeric_limits<real>::epsilon();
1008 alp0 = alp1.nearest(f.transpolar() ? 2U : 1U);
1009 if (f.transpolar()) {
1010 swap(tht1, phi1);
1011 alp1.reflect(false, false, true);
1012 }
1013 const Geodesic3& tg = f.tg();
1014 if (!f.transpolar() && phi1.s() == 0 && fabs(alp1.c()) <= Math::sq(eps))
1015 alp1 = ang(alp1.s(), - Math::sq(eps), alp1.n(), true);
1016 Ex = signbit(alp1.s()) ? -1 : 1;
1017 Nx = signbit(alp1.c()) ? -1 : 1;
1018 tht1 = tht1.flipsign(Ex);
1019 if (f.gammax() > 0) {
1020 phi0 = phi1.nearest(2U);
1021 psi1 = ang(f.kx() * phi1.s(),
1022 phi0.c() * alp1.c() *
1023 hypot(f.kx() * phi1.c(), f.kxp() * tht1.c()));
1024 // k = 1, kp = 0, sqrt(-mu) = bet1.c() * fabs(alp1.s())
1025 // modang(psi1, sqrt(-mu)) = atan2(bet1.s() * fabs(alp1.s()),
1026 // bet0.c() * alp1.c());
1027 // assume fbet().fwd(x) = x in this case
1028 u0 = f.fpsi().fwd(psi1.radians());
1029 v0 = f.ftht().fwd(tht1.radians());
1030 delta = (biaxspecial(tg, f.gammax()) ?
1031 atan2(phi1.s() * fabs(alp1.s()), phi0.c() * alp1.c())
1032 - sqrt(f.gammax()) * f.fpsi().df(u0)
1033 : f.fpsi()(u0)) - f.ftht()(v0);
1034 } else if (f.gammax() == 0) {
1035 if (f.kxp2() == 0) {
1036 // meridonal geodesic on biaxial ellipsoid
1037 // N.B. factor of sqrt(f.gammax()) omitted
1038 // Implicitly assume phi1 in [-90, 90] for now
1039
1040 // phi1, tht1, alp1 are starting conditions.
1041
1042 // If alp1 = 0/180, alp1.s() == 0, and phi1 != +/-90, phi1.c() != 0,
1043 // then, at baseline equator crossing, phi = phi1.nearest(2U), tht =
1044 // tht0 = tht1, alp = alp1
1045 phi0 = phi1.nearest(2U); // phi0 = 0
1046 // psi1 not used, perhaps it should be?
1047 // psi1 = ang(phi1.s(), phi0.c() * alp1.c() * fabs(phi1.c())));
1048 tht0 = tht1;
1049
1050 // Othersise at pole, phi1.c() == 0. Define baseline equator crossing
1051 // by alp = alp1.nearest(2U),
1052 // phi = phi1 + cardinal(signbit(alp1.c()) ? -1 : 1)
1053 // tht0 = tht1 +
1054 // (alp1.nearest(2U)-alp1).flipsign(phi1.s())
1055 if (phi1.c() == 0) {
1056 // phi0 = phi1 + ang::cardinal(signbit(alp1.c()) ? -1 : 1);
1057 tht0 += (alp1.nearest(2U)-alp1).flipsign(phi1.s() * Ex);
1058 }
1059 u0 = f.fpsi().fwd((phi1-phi0).radians());
1060 v0 = 0;
1061 delta = -f.ftht()(tht0.radians());
1062 } else {
1063 // N.B. factor of k*kp omitted
1064 // phi0, tht0 are the middle of the initial umbilical segment
1065 if (fabs(phi1.c()) < 8*eps && fabs(tht1.c()) < 8*eps) {
1066 phi0 = phi1.nearest(1U) + ang::cardinal(Nx);
1067 tht0 = tht1.nearest(1U) + ang::cardinal(1);
1068 delta = f.deltashift()/2 - log(fabs(alp1.t()));
1069 } else {
1070 phi0 = phi1.nearest(2U);
1071 tht0 = tht1.nearest(2U);
1072 delta = Nx * f.fpsi()(lamang(phi1 - phi0, tg.kp())) -
1073 f.ftht()(lamang(tht1 - tht0, tg.k()));
1074 }
1075 }
1076 } else {
1077 // gamma = NaN
1078 }
1079 }
1080
1082 ang& bet10, ang& omg10, ang& alp10)
1083 const {
1084 bet10 = phi1; omg10 = tht1.flipsign(Ex);
1085 alp10 = alp1;
1086 if (transpolar) {
1087 swap(bet10, omg10);
1088 alp10.reflect(false, false, true);
1089 }
1090 omg10 += ang::cardinal(1);
1091 }
1092
1093 void GeodesicLine3::fline::fics::setquadrant(const fline& f, unsigned q) {
1094 ang bet1, omg1, alp1x; // TODO: Fix so fics uses tau1
1095 pos1(f.transpolar(), bet1, omg1, alp1x);
1096 alp1x.setquadrant(q);
1097 *this = fics(f, bet1, omg1, alp1x);
1098 }
1099
1100 GeodesicLine3::gline::gics::gics(const gline& g, const fline::fics& fic) {
1101 if (g.gammax() > 0) {
1102 sig1 = g.gpsi()(fic.u0) + g.gtht()(fic.v0);
1103 } else if (g.gammax() == 0) {
1104 sig1 = g.kxp2() == 0 ? fic.Nx * g.gpsi()(fic.u0) :
1105 fic.Nx * g.gpsi()(lamang(fic.phi1 - fic.phi0, g.kxp())) +
1106 g.gtht()(lamang(fic.tht1 - fic.tht0, g.kx()));
1107 } else {
1108 // gamma = NaN
1109 }
1110 }
1111
1112 Math::real GeodesicLine3::gline::dist(gics ic, fline::disttx d) const {
1113 real sig2 = gpsi()(d.phiw2) + gtht()(d.thtw2) + d.ind2 * 2*s0;
1114 return (sig2 - ic.sig1) * tg().b();
1115 }
1116
1117 GeodesicLine3::hfun::hfun(bool distp, real kap, real kapp, real eps, real mu,
1118 const Geodesic3& tg)
1119 : _kap(kap)
1120 , _kapp(kapp)
1121 , _eps(eps)
1122 , _mu(mu)
1123 , _sqrtmu(sqrt(fabs(_mu)))
1124 , _sqrtkap(sqrt(_kap))
1125 , _sqrtkapp(sqrt(_kapp))
1126 , _distp(distp)
1127 , _umb(!tg.biaxial() && _mu == 0)
1128 , _meridr(_kap == 0 && _mu == 0)
1129 , _meridl(_kapp == 0 && _mu == 0)
1130 , _biaxr(biaxspecial(tg, _mu) && _kap == 0)
1131 , _biaxl(biaxspecial(tg, _mu) && _kapp == 0)
1132 {
1133 // mu in [-kap, kapp], eps in (-inf, 1/kap)
1134 if (!_distp) {
1135 if (_meridr || _biaxr) {
1136 // biaxial rotating coordinate
1137 // _kapp == 1, mu < 0 not allowed
1138 _tx = false;
1139 // f multiplied by sqrt(mu)
1140 _fun = TrigfunExt(
1141 [eps = _eps, mu = _mu]
1142 (real tht) -> real
1143 // This is a trivial case f' = 1
1144 { return fthtbiax(tht, eps, mu); },
1145 Math::pi()/2, false);
1146 } else if (_meridl || _biaxl) {
1147 // biaxial librating coordinate
1148 // _kap == 1, mu > 0 not allowed
1149 // DON'T USE tx: _tx = _mu < 0 && -_mu < tg._ellipthresh;
1150 _tx = false;
1151 // f explicitly multiplied by sqrt(-mu) in operator()() for _biaxl
1152 // For _meridl operator() ignores this
1153 _fun = TrigfunExt(
1154 [eps = _eps, mu = _mu]
1155 (real psi) -> real
1156 { return dfpsibiax(sin(psi), cos(psi), eps, mu); },
1157 Math::pi()/2, false);
1158 } else if (_mu > 0) {
1159 _tx = _mu / (_kap + _mu) < tg._ellipthresh;
1160 if (_tx) {
1161 _ell = EllipticFunction(_kap / (_kap + _mu), 0,
1162 _mu / (_kap + _mu), 1);
1163 _fun = TrigfunExt(
1164 [kap = _kap, kapp = _kapp,
1165 eps = _eps, mu = _mu, ell = _ell]
1166 (real u) -> real
1167 { real sn, cn, dn; (void) ell.am(u, sn, cn, dn);
1168 return fup(cn, kap, kapp, eps, mu); },
1169 _ell.K(), false);
1170 } else
1171 _fun = TrigfunExt(
1172 [kap = _kap, kapp = _kapp, eps = _eps, mu = _mu]
1173 (real tht) -> real
1174 { return fthtp(cos(tht), kap, kapp, eps, mu); },
1175 Math::pi()/2, false);
1176 } else if (_mu < 0) {
1177 _tx = -_mu / _kap < tg._ellipthresh;
1178 if (_tx) {
1179 _ell = EllipticFunction((_kap + _mu) / _kap, 0, -_mu / _kap, 1);
1180 _fun = TrigfunExt(
1181 [kap = _kap, kapp = _kapp,
1182 eps = _eps, mu = _mu, ell = _ell]
1183 (real v) -> real
1184 { real sn, cn, dn; (void) ell.am(v, sn, cn, dn);
1185 return fvp(dn, kap, kapp, eps, mu); },
1186 _ell.K(), false);
1187 } else
1188 _fun = TrigfunExt(
1189 [kap = _kap, kapp = _kapp, eps = _eps, mu = _mu]
1190 (real psi) -> real
1191 { return fpsip(sin(psi), cos(psi),
1192 kap, kapp, eps, mu); },
1193 Math::pi()/2, false);
1194 } else if (_umb) {
1195 _tx = _kapp < tg._ellipthresh;
1196 // f multiplied by sqrt(kap*kapp)
1197 // Include scale = 1 in TrigfunExt constructor because this function
1198 // gets added to u.
1199 if (_tx) {
1200 _ell = EllipticFunction(_kap, 0, _kapp, 1);
1201 _fun = TrigfunExt(
1202 [kap = _kap, kapp = _kapp,
1203 eps = _eps, ell = _ell]
1204 (real v) -> real
1205 { real sn, cn, dn; (void) ell.am(v, sn, cn, dn);
1206 return dfvp(cn, dn, kap, kapp, eps); },
1207 2 * _ell.K(), true, 1);
1208 } else
1209 _fun = TrigfunExt(
1210 [
1211#if defined(_MSC_VER)
1212 // Visual Studio requires the capture of this.
1213 // I can't see why -- probably a bug?
1214 this,
1215#endif
1216 kap = _kap, kapp = _kapp, eps = _eps]
1217 (real tht) -> real
1218 { return dfp(cos(tht), kap, kapp, eps); },
1219 Math::pi(), true, 1);
1220 } else {
1221 // _mu == NaN
1222 _tx = false;
1223 }
1224 } else { // _distp
1225 if (_meridr || _biaxr) {
1226 // biaxial symmetry coordinate
1227 // _kapp == 1, mu < 0 not allowed
1228 _tx = false;
1229 _fun = TrigfunExt(
1230 [eps = _eps, mu = _mu]
1231 (real tht) -> real
1232 // degenerate f' = 0
1233 { return gthtbiax(tht, eps, mu); },
1234 Math::pi()/2, false);
1235 } else if (_meridl || _biaxl) {
1236 // biaxial non-symmetry coordinate
1237 // _kap == 1, mu > 0 not allowed
1238 // DON'T USE tx: _tx = _mu < 0 && -_mu < tg._ellipthresh;
1239 _tx = false;
1240 _fun = TrigfunExt(
1241 [eps = _eps, mu = _mu]
1242 (real psi) -> real
1243 { return gpsibiax(sin(psi), cos(psi), eps, mu); },
1244 Math::pi()/2, false);
1245 } else if (_mu > 0) {
1246 _tx = _mu / (_kap + _mu) < tg._ellipthresh;
1247 if (_tx) {
1248 _ell = EllipticFunction(_kap / (_kap + _mu), 0,
1249 _mu / (_kap + _mu), 1);
1250 _fun = TrigfunExt(
1251 [kap = _kap, kapp = _kapp,
1252 eps = _eps, mu = _mu, ell = _ell]
1253 (real u) -> real
1254 { real sn, cn, dn; (void) ell.am(u, sn, cn, dn);
1255 return gup(cn, dn, kap, kapp, eps, mu); },
1256 _ell.K());
1257 } else
1258 _fun = TrigfunExt(
1259 [kap = _kap, kapp = _kapp, eps = _eps, mu = _mu]
1260 (real tht) -> real
1261 { return gthtp(cos(tht), kap, kapp, eps, mu); },
1262 Math::pi()/2);
1263 } else if (_mu < 0) {
1264 _tx = -_mu / _kap < tg._ellipthresh;
1265 if (_tx) {
1266 _ell = EllipticFunction((_kap + _mu) / _kap, 0, -_mu / _kap, 1);
1267 _fun = TrigfunExt(
1268 [kap = _kap, kapp = _kapp,
1269 eps = _eps, mu = _mu, ell = _ell]
1270 (real v) -> real
1271 { real sn, cn, dn; (void) ell.am(v, sn, cn, dn);
1272 return gvp(cn, dn, kap, kapp, eps, mu); },
1273 _ell.K());
1274 } else
1275 _fun = TrigfunExt(
1276 [kap = _kap, kapp = _kapp, eps = _eps, mu = _mu]
1277 (real psi) -> real
1278 { return gpsip(sin(psi), cos(psi),
1279 kap, kapp, eps, mu); },
1280 Math::pi()/2);
1281 } else if (_umb) {
1282 _tx = _kapp < tg._ellipthresh;
1283 if (_tx) {
1284 _ell = EllipticFunction(_kap, 0, _kapp, 1);
1285 _fun = TrigfunExt(
1286 [kap = _kap, kapp = _kapp, eps = _eps, ell = _ell]
1287 (real v) -> real
1288 { real sn, cn, dn; (void) ell.am(v, sn, cn, dn);
1289 return g0vp(cn, kap, kapp, eps); },
1290 2*_ell.K(), true);
1291 } else
1292 _fun = TrigfunExt(
1293 [kap = _kap, kapp = _kapp, eps = _eps]
1294 (real tht) -> real
1295 { return g0p(cos(tht), kap, kapp, eps); },
1296 Math::pi(), true);
1297 } else {
1298 // _mu == NaN
1299 _tx = false;
1300 }
1301 }
1302 // N.B. _max < 0 for _umb && eps < 0
1303 _max = _umb ? _fun(_tx ? _ell.K() : Math::pi()/2) :
1304 !_distp ? ( _biaxl ? Math::pi()/2 + _sqrtmu * _fun.Max() :
1305 _fun.Max() ) :
1306 _meridl ? _fun(Math::pi()/2) : _fun.Max();
1307 }
1308
1309 Math::real GeodesicLine3::hfun::operator()(real u) const {
1310 if (!_distp) {
1311 if (_biaxl)
1312 // This is sqrt(-mu) * f(u)
1313 return modang(u, _sqrtmu) - _sqrtmu * _fun(u);
1314 else if (_meridl)
1315 return 0;
1316 else if (_umb) {
1317 // This is sqrt(kap * kapp) * f(u)
1318 real phi = gd(u, _sqrtkapp);
1319 return u - _fun(_tx ? _ell.F(phi) : phi);
1320 } else
1321 return _fun(u);
1322 } else {
1323 if (_umb) {
1324 real phi = gd(u, _sqrtkapp);
1325 return _fun(_tx ? _ell.F(phi) : phi);
1326 } else
1327 return _fun(u);
1328 }
1329 }
1330
1331 Math::real GeodesicLine3::hfun::deriv(real u) const {
1332 if (!_distp) {
1333 if (_biaxl)
1334 // This is sqrt(-mu) * f'(u)
1335 return _sqrtmu / (Math::sq(cos(u)) - _mu * Math::sq(sin(u)))
1336 - _sqrtmu * _fun.deriv(u);
1337 else if (_meridl)
1338 return 0;
1339 else if (_umb) {
1340 // This is sqrt(kap * kapp) * f'(u)
1341 real phi = gd(u, _sqrtkapp),
1342 t = _kapp + Math::sq(sinh(u));
1343 // dphi/du = _sqrtkapp * cosh(u) / t;
1344 // for tx w = F(phi, sqrtkap)
1345 // dw/dphi = 1/sqrt(kapp + kap*cos(phi)^2)
1346 // = sqrt(t)/(sqrtkapp *cosh(u))
1347 // N.B. cos(phi)^2 = kapp/t
1348 // dw/du = dw/dphi*dphi/du = 1/sqrt(t)
1349 return 1 - _fun.deriv(_tx ? _ell.F(phi) : phi) /
1350 ( _tx ? sqrt(t) : t / (_sqrtkapp * cosh(u)) );
1351 } else
1352 return _fun.deriv(u);
1353 } else {
1354 if (_umb) {
1355 real phi = gd(u, _sqrtkapp),
1356 t = _kapp + Math::sq(sinh(u));
1357 // See comments in ffun::deriv
1358 return _fun.deriv(_tx ? _ell.F(phi) : phi) /
1359 ( _tx ? sqrt(t) : t / (_sqrtkapp * cosh(u)) );
1360 } else
1361 return _fun.deriv(u);
1362 }
1363 }
1364
1365 Math::real GeodesicLine3::hfun::root(real z, real u0,
1366 int* countn, int* countb, real tol)
1367 const {
1368 if (!_distp) {
1369 if (!isfinite(z)) return z; // Deals with +/-inf and nan
1370 if (_biaxl) {
1371 // z = 1/sqrt(-mu)
1372 // here f(u) = atan(sqrt(-mu)*tan(u))/sqrt(-mu)-Deltaf(u)
1373 // let fx(u) = sqrt(-mu) * f(u); fun(u) = sqrt(-mu)*Deltaf(u)
1374 // z = fx(u) = modang(u, sqrt(-mu)) - fun(u)
1375 // fun(u) is monotonically increasing/decreasing quasilinear function
1376 // period pi. Combined function is quasilinear
1377 // z = s * u +/- m; period = pi
1378 // Inverting:
1379 // u = z/s +/- m/s; period s*pi
1380 // u = fxinv(z) = modang(z/x, 1/sqrt(-mu)) + funinv(z)
1381 // funinv(z) periodic function of z period (1-s)*pi
1382 real d = Max(),
1383 ua = (z - d) / Slope(),
1384 ub = (z + d) / Slope();
1385 u0 = fmin(ub, fmax(ua, u0));
1386 return Trigfun::root(Trigfun::FFUNROOT,
1387 [this]
1388 (real u) -> pair<real, real>
1389 { return pair<real, real>((*this)(u), deriv(u)); },
1390 z,
1391 u0, ua, ub,
1392 HalfPeriod(), HalfPeriod()/Slope(), 1,
1393 countn, countb, tol);
1394 } else if (_umb) {
1395 real d = fabs(Max())
1396 + 2 * numeric_limits<real>::epsilon() * fmax(real(1), fabs(z)),
1397 ua = z - d,
1398 ub = z + d;
1399 u0 = fmin(ub, fmax(ua, u0));
1400 return Trigfun::root(Trigfun::FFUNROOT,
1401 [this]
1402 (real u) -> pair<real, real>
1403 { return pair<real, real>((*this)(u), deriv(u)); },
1404 z,
1405 u0, ua, ub,
1406 Math::pi()/2, Math::pi()/2, 1,
1407 countn, countb, tol);
1408 } else
1409 return Math::NaN();
1410 } else {
1411 // This function isn't neeed. General inversion mechanisms in Trigfun
1412 // suffice. NO, the trigfun for _umb is not invertible.
1413 if (!(isfinite(z) && _umb))
1414 return Math::NaN(); // Deals with +/-inf and nan
1415 // Now we're dealing with _umb.
1416 if (fabs(z) >= Max())
1417 return copysign(Geodesic3::BigValue(), z);
1418 real ua = -Geodesic3::BigValue(), ub = -ua;
1419 u0 = fmin(ub, fmax(ua, u0));
1420 // Solve z = _fun(_tx ? _ell.F(gd(u)) : gd(u)) for u
1421 return Trigfun::root(Trigfun::GFUNROOT,
1422 [this]
1423 (real u) -> pair<real, real>
1424 { return pair<real, real>((*this)(u), deriv(u)); },
1425 z,
1426 u0, ua, ub,
1427 Math::pi()/2, Math::pi()/2, 1, countn, countb, tol);
1428 }
1429 }
1430
1431 // Accurate inverse by direct Newton (not using _finv)
1432 Math::real GeodesicLine3::hfun::inv(real z, int* countn, int* countb)
1433 const {
1434 if (!_distp)
1435 return _umb ? root(z, z, countn, countb) :
1436 _biaxl ? root(z, modang(z/Slope(), 1/_sqrtmu), countn, countb) :
1437 _fun.inv1(z, countn, countb);
1438 else { // distp
1439 if (_biaxr) return Math::NaN();
1440 else if (_umb) {
1441 // In limit _eps -> 0
1442 // g(u) = atan(_sqrtkap/_sqrtkapp * tanh(u))
1443 // at u = 0, dg/du = _sqrtkap/_sqrtkapp
1444 // u = inf, g = atan(_sqrtkap/_sqrtkapp)
1445 //
1446 // For _eps finite
1447 // at u = 0, dg/du = _sqrtkap/_sqrtkapp * sqrt(1 - _eps*_kap)
1448 // u = inf, g = _max
1449 // Note: at u = 0
1450 // du/dphi = _sqrtkapp, so
1451 // dg/dphi = _sqrtkap * sqrt(1 - _eps*_kap) -- OK
1452 //
1453 // Approximate g(u) for _eps finite by
1454 // g(u) = _max / atan(_sqrtkap/_sqrtkapp) *
1455 // atan(_sqrtkap/_sqrtkapp *
1456 // tanh(sqrt(1 - _eps*_kap) * atan(_sqrtkap/_sqrtkapp) / _max
1457 // * u))
1458 // Values at +/- inf and slope at origin match.
1459 //
1460 // Solve z = g(u) gives u = u0:
1461 real u0 = atanh(_sqrtkapp/_sqrtkap *
1462 tan(atan(_sqrtkap/_sqrtkapp) / _max * z)) /
1463 (sqrt(1 - _eps*_kap) * atan(_sqrtkap/_sqrtkapp) / _max);
1464 return root(z, u0, countn, countb);
1465 } else
1466 return _fun.inv1(z, countn, countb);
1467 }
1468 }
1469
1470 // _mu > 0 && !_tx
1471 Math::real GeodesicLine3::hfun::fthtp(real c, real kap, real kapp,
1472 real eps, real mu) {
1473 real c2 = kap * Math::sq(c);
1474 return sqrt((1 - eps * c2) / ((kapp + c2) * (c2 + mu)) );
1475 }
1476 // This is non-negative
1477 Math::real GeodesicLine3::hfun::gthtp(real c, real kap, real kapp,
1478 real eps, real mu) {
1479 real c2 = kap * Math::sq(c);
1480 return c2 * sqrt((1 - eps * c2) / ((kapp + c2) * (c2 + mu)) );
1481 }
1482
1483 // _mu > 0 && _tx
1484 Math::real GeodesicLine3::hfun::fup(real cn, real kap, real kapp,
1485 real eps, real mu) {
1486 real c2 = kap * Math::sq(cn);
1487 return sqrt( (1 - eps * c2) / ((kapp + c2) * (kap + mu)) );
1488 }
1489 // This is non-negative
1490 Math::real GeodesicLine3::hfun::gup(real cn, real /* dn */,
1491 real kap, real kapp, real eps, real mu) {
1492 real c2 = kap * Math::sq(cn);
1493 return c2 * sqrt( (1 - eps * c2) / ((kapp + c2) * (kap + mu)) );
1494 }
1495
1496 // _mu == 0 && !_tx
1497 Math::real GeodesicLine3::hfun::dfp(real c, real kap, real kapp, real eps) {
1498 // function dfp = dfpf(phi, kappa, epsilon)
1499 // return derivative of sqrt(kap * kapp) * Delta f
1500 // s = sqrt(1 - kap * sin(phi)^2)
1501 real c2 = kap * Math::sq(c), s = sqrt(kapp + c2);
1502 return eps*kap * sqrt(kapp) * c / (s * (1 + sqrt(1 - eps*c2)));
1503 }
1504 Math::real GeodesicLine3::hfun::g0p(real c, real kap, real kapp, real eps) {
1505 real c2 = kap * Math::sq(c);
1506 return sqrt( kap * (1 - eps * c2) / (kapp + c2) ) * c;
1507 }
1508
1509 // _mu == 0 && _tx
1510 Math::real GeodesicLine3::hfun::dfvp(real cn, real /* dn */,
1511 real kap, real kapp, real eps) {
1512 // function dfvp = dfvpf(v, kap, eps)
1513 // return derivative of sqrt(kap * kapp) * Delta f_v
1514 return eps*kap * sqrt(kapp) * cn /
1515 (1 + sqrt(1 - eps*kap * Math::sq(cn)));
1516 }
1517 Math::real GeodesicLine3::hfun::g0vp(real cn, real kap, real /* kapp */,
1518 real eps) {
1519 real c2 = kap * Math::sq(cn);
1520 return sqrt( kap * (1 - eps * c2) ) * cn;
1521 }
1522
1523 // _mu < 0 && !_tx
1524 Math::real GeodesicLine3::hfun::fpsip(real s, real c, real kap, real kapp,
1525 real eps, real mu) {
1526 real c2 = kap * Math::sq(c) - mu * Math::sq(s);
1527 return sqrt( (1 - eps * c2) / ((kapp + c2) * c2) ) ;
1528 }
1529 // This is positive
1530 Math::real GeodesicLine3::hfun::gpsip(real s, real c, real kap, real kapp,
1531 real eps, real mu) {
1532 real c2 = kap * Math::sq(c) - mu * Math::sq(s);
1533 return sqrt(c2 * (1 - eps * c2) / (kapp + c2)) ;
1534 }
1535
1536 // _mu < 0 && _tx
1537 Math::real GeodesicLine3::hfun::fvp(real dn, real kap, real kapp,
1538 real eps, real /* mu */) {
1539 real c2 = kap * Math::sq(dn);
1540 return sqrt( (1 - eps * c2) / ((kapp + c2) * kap) );
1541 }
1542 // This is positive
1543 Math::real GeodesicLine3::hfun::gvp(real /* cn */, real dn,
1544 real kap, real kapp,
1545 real eps, real /* mu */) {
1546 real dn2 = Math::sq(dn), c2 = kap * dn2;
1547 return dn2 * sqrt( kap * (1 - eps * c2) / (kapp + c2) );
1548 }
1549
1550 // biaxial variants for kap = 0, kapp = 1, mu > 0
1552 GeodesicLine3::hfun::fthtbiax(real /* tht */, real /* eps */,
1553 real /* mu */) {
1554 // Multiply by f functions by sqrt(abs(mu))
1555 // return 1 / sqrt(mu);
1556 return 1;
1557 }
1559 GeodesicLine3::hfun::gthtbiax(real /* tht */, real /* eps */,
1560 real /* mu */) {
1561 return 0;
1562 }
1563
1564 // biaxial variants for kap = 1, kapp = 0, mu <= 0, !_tx
1566 GeodesicLine3::hfun::dfpsibiax(real s, real c, real eps, real mu) {
1567 real c2 = Math::sq(c) - mu * Math::sq(s);
1568 // f functions are multiplied by sqrt(abs(mu)) but don't include this
1569 // factor here; instead include it in operator()(). etc. This was we can
1570 // still use this function in the limit mu -> 0 to determine the conjugate
1571 // point on a meridian.
1572 return eps / (1 + sqrt(1 - eps * c2));
1573 }
1575 GeodesicLine3::hfun::gpsibiax(real s, real c, real eps, real mu) {
1576 real c2 = Math::sq(c) - mu * Math::sq(s);
1577 // return gpsip(s, c, 1, 0, eps, mu) but with the factor c2 canceled
1578 return sqrt(1 - eps * c2);
1579 }
1580
1581 } // namespace Triaxial
1582} // namespace GeographicLib
GeographicLib::Angle ang
GeographicLib::Math::real real
Header for GeographicLib::Triaxial::GeodesicLine3 class.
AngleT rebase(const AngleT &c) const
Definition Angle.hpp:647
AngleT base() const
Definition Angle.hpp:642
static AngleT cardinal(Math::real q)
AngleT flipsign(T mult) const
Definition Angle.hpp:705
static AngleT radians(T rad)
Definition Angle.hpp:534
T ncardinal() const
Definition Angle.hpp:572
AngleT & setn(T n=0)
Definition Angle.hpp:662
AngleT & setquadrant(unsigned q)
Definition Angle.hpp:682
AngleT nearest(unsigned ind=0U) const
Definition Angle.hpp:743
AngleT & reflect(bool flips, bool flipc=false, bool swapp=false)
Definition Angle.hpp:696
Exception handling for GeographicLib.
static T sq(T x)
Definition Math.hpp:209
static T clamp(T x, T a, T b)
Definition Math.cpp:296
static T infinity()
Definition Math.cpp:308
static T pi()
Definition Math.hpp:187
static T NaN()
Definition Math.cpp:301
static bool AngNorm(Angle &bet, Angle &omg, Angle &alp, bool alt=false)
The solution of the geodesic problem for a triaxial ellipsoid.
Definition Geodesic3.hpp:65
void pos1(bool transpolar, ang &bet10, ang &omg10, ang &alp10) const
void pos1(Angle &bet1, Angle &omg1, Angle &alp1) const
void Position(real s12, Angle &bet2, Angle &omg2, Angle &alp2) const
A function defined by its derivative and its inverse.
Definition Trigfun.hpp:498
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)