Coverage Report

Created: 2025-07-23 06:58

/src/PROJ/src/phi2.cpp
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
}