-
Notifications
You must be signed in to change notification settings - Fork 807
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #2397 from cffk/merc-update
Update Mercator projection, more accurate, faster
- Loading branch information
Showing
12 changed files
with
344 additions
and
185 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,68 +1,134 @@ | ||
/* Determine latitude angle phi-2. */ | ||
|
||
#include <math.h> | ||
#include <limits> | ||
#include <algorithm> | ||
|
||
#include "proj.h" | ||
#include "proj_internal.h" | ||
|
||
static const double TOL = 1.0e-10; | ||
static const int N_ITER = 15; | ||
double pj_sinhpsi2tanphi(projCtx ctx, const double taup, const double e) { | ||
/**************************************************************************** | ||
* Convert tau' = sinh(psi) = tan(chi) to tau = tan(phi). The code is taken | ||
* from GeographicLib::Math::tauf(taup, e). | ||
* | ||
* Here | ||
* phi = geographic latitude (radians) | ||
* psi is the isometric latitude | ||
* psi = asinh(tan(phi)) - e * atanh(e * sin(phi)) | ||
* = asinh(tan(chi)) | ||
* chi is the conformal latitude | ||
* | ||
* The representation of latitudes via their tangents, tan(phi) and tan(chi), | ||
* maintains full *relative* accuracy close to latitude = 0 and +/- pi/2. | ||
* This is sometimes important, e.g., to compute the scale of the transverse | ||
* Mercator projection which involves cos(phi)/cos(chi) tan(phi) | ||
* | ||
* From Karney (2011), Eq. 7, | ||
* | ||
* tau' = sinh(psi) = sinh(asinh(tan(phi)) - e * atanh(e * sin(phi))) | ||
* = tan(phi) * cosh(e * atanh(e * sin(phi))) - | ||
* sec(phi) * sinh(e * atanh(e * sin(phi))) | ||
* = tau * sqrt(1 + sigma^2) - sqrt(1 + tau^2) * sigma | ||
* where | ||
* sigma = sinh(e * atanh( e * tau / sqrt(1 + tau^2) )) | ||
* | ||
* For e small, | ||
* | ||
* tau' = (1 - e^2) * tau | ||
* | ||
* The relation tau'(tau) can therefore by reliably inverted by Newton's | ||
* method with | ||
* | ||
* tau = tau' / (1 - e^2) | ||
* | ||
* as an initial guess. Newton's method requires dtau'/dtau. Noting that | ||
* | ||
* dsigma/dtau = e^2 * sqrt(1 + sigma^2) / | ||
* (sqrt(1 + tau^2) * (1 + (1 - e^2) * tau^2)) | ||
* d(sqrt(1 + tau^2))/dtau = tau / sqrt(1 + tau^2) | ||
* | ||
* we have | ||
* | ||
* dtau'/dtau = (1 - e^2) * sqrt(1 + tau'^2) * sqrt(1 + tau^2) / | ||
* (1 + (1 - e^2) * tau^2) | ||
* | ||
* This works fine unless tau^2 and tau'^2 overflows. This may be partially | ||
* cured by writing, e.g., sqrt(1 + tau^2) as hypot(1, tau). However, nan | ||
* will still be generated with tau' = inf, since (inf - inf) will appear in | ||
* the Newton iteration. | ||
* | ||
* If we note that for sufficiently large |tau|, i.e., |tau| >= 2/sqrt(eps), | ||
* sqrt(1 + tau^2) = |tau| and | ||
* | ||
* tau' = exp(- e * atanh(e)) * tau | ||
* | ||
* So | ||
* | ||
* tau = exp(e * atanh(e)) * tau' | ||
* | ||
* can be returned unless |tau| >= 2/sqrt(eps); this then avoids overflow | ||
* problems for large tau' and returns the correct result for tau' = +/-inf | ||
* and nan. | ||
* | ||
* Newton's method usually take 2 iterations to converge to double precision | ||
* accuracy (for WGS84 flattening). However only 1 iteration is needed for | ||
* |chi| < 3.35 deg. In addition, only 1 iteration is needed for |chi| > | ||
* 89.18 deg (tau' > 70), if tau = exp(e * atanh(e)) * tau' is used as the | ||
* starting guess. | ||
****************************************************************************/ | ||
|
||
constexpr int numit = 5; | ||
// min iterations = 1, max iterations = 2; mean = 1.954 | ||
static const double rooteps = sqrt(std::numeric_limits<double>::epsilon()); | ||
static const double tol = rooteps / 10; // the criterion for Newton's method | ||
static const double tmax = 2 / rooteps; // threshold for large arg limit exact | ||
const double e2m = 1 - e * e; | ||
const double stol = tol * std::max(1.0, fabs(taup)); | ||
// The initial guess. 70 corresponds to chi = 89.18 deg (see above) | ||
double tau = fabs(taup) > 70 ? taup * exp(e * atanh(e)) : taup / e2m; | ||
if (!(fabs(tau) < tmax)) // handles +/-inf and nan and e = 1 | ||
return tau; | ||
// If we need to deal with e > 1, then we could include: | ||
// if (e2m < 0) return std::numeric_limits<double>::quiet_NaN(); | ||
int i = numit; | ||
for (; i; --i) { | ||
double tau1 = sqrt(1 + tau * tau); | ||
double sig = sinh( e * atanh(e * tau / tau1) ); | ||
double taupa = sqrt(1 + sig * sig) * tau - sig * tau1; | ||
double dtau = ( (taup - taupa) * (1 + e2m * (tau * tau)) / | ||
(e2m * tau1 * sqrt(1 + taupa * taupa)) ); | ||
tau += dtau; | ||
if (!(fabs(dtau) >= stol)) // backwards test to allow nans to succeed. | ||
break; | ||
} | ||
if (i == 0) | ||
pj_ctx_set_errno(ctx, PJD_ERR_NON_CONV_SINHPSI2TANPHI); | ||
return tau; | ||
} | ||
|
||
/*****************************************************************************/ | ||
double pj_phi2(projCtx ctx, const double ts0, const double e) { | ||
/****************************************************************************** | ||
Determine latitude angle phi-2. | ||
Inputs: | ||
ts = exp(-psi) where psi is the isometric latitude (dimensionless) | ||
e = eccentricity of the ellipsoid (dimensionless) | ||
Output: | ||
phi = geographic latitude (radians) | ||
Here isometric latitude is defined by | ||
psi = log( tan(pi/4 + phi/2) * | ||
( (1 - e*sin(phi)) / (1 + e*sin(phi)) )^(e/2) ) | ||
= asinh(tan(phi)) - e * atanh(e * sin(phi)) | ||
This routine inverts this relation using the iterative scheme given | ||
by Snyder (1987), Eqs. (7-9) - (7-11) | ||
*******************************************************************************/ | ||
const double eccnth = .5 * e; | ||
double ts = ts0; | ||
#ifdef no_longer_used_original_convergence_on_exact_dphi | ||
double Phi = M_HALFPI - 2. * atan(ts); | ||
#endif | ||
int i = N_ITER; | ||
|
||
for(;;) { | ||
/* | ||
* sin(Phi) = sin(PI/2 - 2* atan(ts)) | ||
* = cos(2*atan(ts)) | ||
* = 2*cos^2(atan(ts)) - 1 | ||
* = 2 / (1 + ts^2) - 1 | ||
* = (1 - ts^2) / (1 + ts^2) | ||
*/ | ||
const double sinPhi = (1 - ts * ts) / (1 + ts * ts); | ||
const double con = e * sinPhi; | ||
double old_ts = ts; | ||
ts = ts0 * pow((1. - con) / (1. + con), eccnth); | ||
#ifdef no_longer_used_original_convergence_on_exact_dphi | ||
/* The convergence criterion is nominally on exact dphi */ | ||
const double newPhi = M_HALFPI - 2. * atan(ts); | ||
const double dphi = newPhi - Phi; | ||
Phi = newPhi; | ||
#else | ||
/* If we don't immediately update Phi from this, we can | ||
* change the conversion criterion to save us computing atan() at each step. | ||
* Particularly we can observe that: | ||
* |atan(ts) - atan(old_ts)| <= |ts - old_ts| | ||
* So if |ts - old_ts| matches our convergence criterion, we're good. | ||
*/ | ||
const double dphi = 2 * (ts - old_ts); | ||
#endif | ||
if (fabs(dphi) > TOL && --i) { | ||
continue; | ||
} | ||
break; | ||
} | ||
if (i <= 0) | ||
pj_ctx_set_errno(ctx, PJD_ERR_NON_CON_INV_PHI2); | ||
return M_HALFPI - 2. * atan(ts); | ||
/**************************************************************************** | ||
* Determine latitude angle phi-2. | ||
* Inputs: | ||
* ts = exp(-psi) where psi is the isometric latitude (dimensionless) | ||
* this variable is defined in Snyder (1987), Eq. (7-10) | ||
* e = eccentricity of the ellipsoid (dimensionless) | ||
* Output: | ||
* phi = geographic latitude (radians) | ||
* Here isometric latitude is defined by | ||
* psi = log( tan(pi/4 + phi/2) * | ||
* ( (1 - e*sin(phi)) / (1 + e*sin(phi)) )^(e/2) ) | ||
* = asinh(tan(phi)) - e * atanh(e * sin(phi)) | ||
* = asinh(tan(chi)) | ||
* chi = conformal latitude | ||
* | ||
* This routine converts t = exp(-psi) to | ||
* | ||
* tau' = tan(chi) = sinh(psi) = (1/t - t)/2 | ||
* | ||
* returns atan(sinpsi2tanphi(tau')) | ||
***************************************************************************/ | ||
return atan(pj_sinhpsi2tanphi(ctx, (1/ts0 - ts0) / 2, e)); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.