Line | Count | Source (jump to first uncovered line) |
1 | | /* Determine latitude angle phi-2. */ |
2 | | |
3 | | #include <algorithm> |
4 | | #include <limits> |
5 | | #include <math.h> |
6 | | |
7 | | #include "proj.h" |
8 | | #include "proj_internal.h" |
9 | | |
10 | 0 | double pj_sinhpsi2tanphi(PJ_CONTEXT *ctx, const double taup, const double e) { |
11 | | /*************************************************************************** |
12 | | * Convert tau' = sinh(psi) = tan(chi) to tau = tan(phi). The code is taken |
13 | | * from GeographicLib::Math::tauf(taup, e). |
14 | | * |
15 | | * Here |
16 | | * phi = geographic latitude (radians) |
17 | | * psi is the isometric latitude |
18 | | * psi = asinh(tan(phi)) - e * atanh(e * sin(phi)) |
19 | | * = asinh(tan(chi)) |
20 | | * chi is the conformal latitude |
21 | | * |
22 | | * The representation of latitudes via their tangents, tan(phi) and |
23 | | * tan(chi), maintains full *relative* accuracy close to latitude = 0 and |
24 | | * +/- pi/2. This is sometimes important, e.g., to compute the scale of the |
25 | | * transverse Mercator projection which involves cos(phi)/cos(chi) tan(phi) |
26 | | * |
27 | | * From Karney (2011), Eq. 7, |
28 | | * |
29 | | * tau' = sinh(psi) = sinh(asinh(tan(phi)) - e * atanh(e * sin(phi))) |
30 | | * = tan(phi) * cosh(e * atanh(e * sin(phi))) - |
31 | | * sec(phi) * sinh(e * atanh(e * sin(phi))) |
32 | | * = tau * sqrt(1 + sigma^2) - sqrt(1 + tau^2) * sigma |
33 | | * where |
34 | | * sigma = sinh(e * atanh( e * tau / sqrt(1 + tau^2) )) |
35 | | * |
36 | | * For e small, |
37 | | * |
38 | | * tau' = (1 - e^2) * tau |
39 | | * |
40 | | * The relation tau'(tau) can therefore by reliably inverted by Newton's |
41 | | * method with |
42 | | * |
43 | | * tau = tau' / (1 - e^2) |
44 | | * |
45 | | * as an initial guess. Newton's method requires dtau'/dtau. Noting that |
46 | | * |
47 | | * dsigma/dtau = e^2 * sqrt(1 + sigma^2) / |
48 | | * (sqrt(1 + tau^2) * (1 + (1 - e^2) * tau^2)) |
49 | | * d(sqrt(1 + tau^2))/dtau = tau / sqrt(1 + tau^2) |
50 | | * |
51 | | * we have |
52 | | * |
53 | | * dtau'/dtau = (1 - e^2) * sqrt(1 + tau'^2) * sqrt(1 + tau^2) / |
54 | | * (1 + (1 - e^2) * tau^2) |
55 | | * |
56 | | * This works fine unless tau^2 and tau'^2 overflows. This may be partially |
57 | | * cured by writing, e.g., sqrt(1 + tau^2) as hypot(1, tau). However, nan |
58 | | * will still be generated with tau' = inf, since (inf - inf) will appear in |
59 | | * the Newton iteration. |
60 | | * |
61 | | * If we note that for sufficiently large |tau|, i.e., |tau| >= 2/sqrt(eps), |
62 | | * sqrt(1 + tau^2) = |tau| and |
63 | | * |
64 | | * tau' = exp(- e * atanh(e)) * tau |
65 | | * |
66 | | * So |
67 | | * |
68 | | * tau = exp(e * atanh(e)) * tau' |
69 | | * |
70 | | * can be returned unless |tau| >= 2/sqrt(eps); this then avoids overflow |
71 | | * problems for large tau' and returns the correct result for tau' = +/-inf |
72 | | * and nan. |
73 | | * |
74 | | * Newton's method usually take 2 iterations to converge to double precision |
75 | | * accuracy (for WGS84 flattening). However only 1 iteration is needed for |
76 | | * |chi| < 3.35 deg. In addition, only 1 iteration is needed for |chi| > |
77 | | * 89.18 deg (tau' > 70), if tau = exp(e * atanh(e)) * tau' is used as the |
78 | | * starting guess. |
79 | | * |
80 | | * For small flattening, |f| <= 1/50, the series expansion in n can be |
81 | | * used: |
82 | | * |
83 | | * Assuming n = e^2 / (1 + sqrt(1 - e^2))^2 is passed as an argument |
84 | | * |
85 | | * double F[int(AuxLat::AUXORDER)]; |
86 | | * pj_auxlat_coeffs(n, AuxLat::CONFORMAL, AuxLat::GEOGRAPHIC, F); |
87 | | * double sphi, cphi; |
88 | | * // schi cchi |
89 | | * pj_auxlat_convert(taup/hypot(1.0, taup), 1/hypot(1.0, taup), |
90 | | * sphi, cphi, F); |
91 | | * return sphi/cphi; |
92 | | **************************************************************************/ |
93 | 0 | constexpr int numit = 5; |
94 | | // min iterations = 1, max iterations = 2; mean = 1.954 |
95 | 0 | static const double rooteps = sqrt(std::numeric_limits<double>::epsilon()); |
96 | 0 | static const double tol = rooteps / 10; // the criterion for Newton's method |
97 | 0 | static const double tmax = |
98 | 0 | 2 / rooteps; // threshold for large arg limit exact |
99 | 0 | const double e2m = 1 - e * e; |
100 | 0 | const double stol = tol * std::max(1.0, fabs(taup)); |
101 | | // The initial guess. 70 corresponds to chi = 89.18 deg (see above) |
102 | 0 | double tau = fabs(taup) > 70 ? taup * exp(e * atanh(e)) : taup / e2m; |
103 | 0 | if (!(fabs(tau) < tmax)) // handles +/-inf and nan and e = 1 |
104 | 0 | return tau; |
105 | | // If we need to deal with e > 1, then we could include: |
106 | | // if (e2m < 0) return std::numeric_limits<double>::quiet_NaN(); |
107 | 0 | int i = numit; |
108 | 0 | for (; i; --i) { |
109 | 0 | double tau1 = sqrt(1 + tau * tau); |
110 | 0 | double sig = sinh(e * atanh(e * tau / tau1)); |
111 | 0 | double taupa = sqrt(1 + sig * sig) * tau - sig * tau1; |
112 | 0 | double dtau = ((taup - taupa) * (1 + e2m * (tau * tau)) / |
113 | 0 | (e2m * tau1 * sqrt(1 + taupa * taupa))); |
114 | 0 | tau += dtau; |
115 | 0 | if (!(fabs(dtau) >= stol)) // backwards test to allow nans to succeed. |
116 | 0 | break; |
117 | 0 | } |
118 | 0 | if (i == 0) |
119 | 0 | proj_context_errno_set(ctx, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); |
120 | 0 | return tau; |
121 | 0 | } |
122 | | |
123 | | /*****************************************************************************/ |
124 | 0 | double pj_phi2(PJ_CONTEXT *ctx, const double ts0, const double e) { |
125 | | /**************************************************************************** |
126 | | * Determine latitude angle phi-2. |
127 | | * Inputs: |
128 | | * ts = exp(-psi) where psi is the isometric latitude (dimensionless) |
129 | | * this variable is defined in Snyder (1987), Eq. (7-10) |
130 | | * e = eccentricity of the ellipsoid (dimensionless) |
131 | | * Output: |
132 | | * phi = geographic latitude (radians) |
133 | | * Here isometric latitude is defined by |
134 | | * psi = log( tan(pi/4 + phi/2) * |
135 | | * ( (1 - e*sin(phi)) / (1 + e*sin(phi)) )^(e/2) ) |
136 | | * = asinh(tan(phi)) - e * atanh(e * sin(phi)) |
137 | | * = asinh(tan(chi)) |
138 | | * chi = conformal latitude |
139 | | * |
140 | | * This routine converts t = exp(-psi) to |
141 | | * |
142 | | * tau' = tan(chi) = sinh(psi) = (1/t - t)/2 |
143 | | * |
144 | | * returns atan(sinpsi2tanphi(tau')) |
145 | | ***************************************************************************/ |
146 | 0 | return atan(pj_sinhpsi2tanphi(ctx, (1 / ts0 - ts0) / 2, e)); |
147 | 0 | } |