TransverseMercatorExact.cpp
Go to the documentation of this file.
00001 /**
00002  * \file TransverseMercatorExact.cpp
00003  * \brief Implementation for GeographicLib::TransverseMercatorExact class
00004  *
00005  * Copyright (c) Charles Karney (2008) <charles@karney.com>
00006  * http://charles.karney.info/geographic
00007  * and licensed under the LGPL.
00008  *
00009  * The relevant section of Lee's paper is part V, pp 67&ndash;101,
00010  * <a href="http://dx.doi.org/10.3138/X687-1574-4325-WM62">Conformal
00011  * Projections Based On Jacobian Elliptic Functions</a>.
00012  *
00013  * The method entails using the Thompson Transverse Mercator as an
00014  * intermediate projection.  The projections from the intermediate
00015  * coordinates to [\e phi, \e lam] and [\e x, \e y] are given by elliptic
00016  * functions.  The inverse of these projections are found by Newton's method
00017  * with a suitable starting guess.
00018  *
00019  * This implementation and notation closely follows Lee, with the following
00020  * exceptions:
00021  * <center><table>
00022  * <tr><th>Lee    <th>here    <th>Description
00023  * <tr><td>x/a    <td>xi      <td>Northing (unit Earth)
00024  * <tr><td>y/a    <td>eta     <td>Easting (unit Earth)
00025  * <tr><td>s/a    <td>sigma   <td>xi + i * eta
00026  * <tr><td>y      <td>x       <td>Easting
00027  * <tr><td>x      <td>y       <td>Northing
00028  * <tr><td>k      <td>e       <td>eccentricity
00029  * <tr><td>k^2    <td>mu      <td>elliptic function parameter
00030  * <tr><td>k'^2   <td>mv      <td>elliptic function complementary parameter
00031  * <tr><td>m      <td>k       <td>scale
00032  * </table></center>
00033  *
00034  * Minor alterations have been made in some of Lee's expressions in an
00035  * attempt to control round-off.  For example atanh(sin(phi)) is replaced by
00036  * asinh(tan(phi)) which maintains accuracy near phi = pi/2.  Such changes
00037  * are noted in the code.
00038  **********************************************************************/
00039 
00040 #include "GeographicLib/TransverseMercatorExact.hpp"
00041 #include "GeographicLib/Constants.hpp"
00042 #include <limits>
00043 #include <algorithm>
00044 
00045 namespace {
00046   char RCSID[] = "$Id: TransverseMercatorExact.cpp 6568 2009-03-01 17:58:41Z ckarney $";
00047   char RCSID_H[] = TRANSVERSEMERCATOREXACT_HPP;
00048 }
00049 
00050 namespace GeographicLib {
00051 
00052   using namespace std;
00053 
00054   const double TransverseMercatorExact::tol = numeric_limits<double>::epsilon();
00055   const double TransverseMercatorExact::tol1 = 0.1 * sqrt(tol);
00056   const double TransverseMercatorExact::tol2 = 0.1 * tol;
00057   const double TransverseMercatorExact::taytol = pow(tol, 0.6);
00058       // Overflow value for asinh(tan(pi/2)) etc.
00059   const double TransverseMercatorExact::ahypover =
00060     (double)(numeric_limits<double>::digits)
00061     /log((double)(numeric_limits<double>::radix)) + 2;
00062 
00063   TransverseMercatorExact::TransverseMercatorExact(double a, double invf,
00064                                                    double k0, bool extendp)
00065     throw()
00066     : _a(a)
00067     , _f(1 / invf)
00068     , _k0(k0)
00069     , _mu(_f * (2 - _f))        // e^2
00070     , _mv(1 - _mu)              // 1 - e^2
00071     , _e(sqrt(_mu))
00072     , _ep2(_mu / _mv)           // e^2 / (1 - e^2)
00073     , _extendp(extendp)
00074     , _Eu(_mu)
00075     , _Ev(_mv)
00076   {}
00077 
00078   const TransverseMercatorExact
00079   TransverseMercatorExact::UTM(Constants::WGS84_a(), Constants::WGS84_invf(),
00080                                Constants::UTM_k0());
00081 
00082   double  TransverseMercatorExact::psi(double phi) const throw() {
00083     double s = sin(phi);
00084     // Lee 9.4.  Rewrite atanh(sin(phi)) = asinh(tan(phi)) which is more
00085     // accurate.  Write tan(phi) this way to ensure that sign(tan(phi)) =
00086     // sign(phi)
00087     return asinh(s / max(cos(phi), 0.1 * tol)) - _e * atanh(_e * s);
00088   }
00089 
00090   double TransverseMercatorExact::psiinv(double psi) const throw() {
00091     // This is the inverse of psi.  Use Newton's method to solve for q in
00092     //
00093     //   psi = q - e * atanh(e * tanh(q))
00094     //
00095     // and then substitute phi = atan(sinh(q)).  Note that
00096     // dpsi/dq = (1 - e^2)/(1 - e^2 * tanh(q)^2)
00097     double q = psi;             // Initial guess
00098     for (int i = 0; i < numit; ++i) {
00099       // min iterations = 1, max iterations = 3; mean = 2.8
00100       double
00101         t = tanh(q),
00102         dq = -(q - _e * atanh(_e * t) - psi) * (1 - _mu * sq(t)) / _mv;
00103       q += dq;
00104       if (abs(dq) < tol1)
00105         break;
00106     }
00107     return atan(sinh(q));
00108   }
00109 
00110   void TransverseMercatorExact::zeta(double u,
00111                                      double snu, double cnu, double dnu,
00112                                      double v,
00113                                      double snv, double cnv, double dnv,
00114                                      double& psi, double& lam) const throw() {
00115     // Lee 54.17 but write
00116     // atanh(snu * dnv) = asinh(snu * dnv / sqrt(cnu^2 + _mv * snu^2 * snv^2))
00117     // atanh(_e * snu / dnv) = asinh(_e * snu / sqrt(_mu * cnu^2 + _mv * cnv^2))
00118     double
00119       d1 = sqrt(sq(cnu) + _mv * sq(snu * snv)),
00120       d2 = sqrt(_mu * sq(cnu) + _mv * sq(cnv));
00121     psi =
00122       // Overflow to values s.t. tanh = 1.
00123       (d1 ? asinh(snu * dnv / d1) : snu < 0 ? -ahypover : ahypover)
00124       - (d2 ? _e * asinh(_e * snu / d2) : snu < 0 ? -ahypover : ahypover);
00125     lam = (d1 != 0 && d2 != 0) ?
00126       atan2(dnu * snv, cnu * cnv) - _e * atan2(_e * cnu * snv, dnu * cnv) :
00127       0;
00128   }
00129 
00130   void TransverseMercatorExact::dwdzeta(double u,
00131                                         double snu, double cnu, double dnu,
00132                                         double v,
00133                                         double snv, double cnv, double dnv,
00134                                         double& du, double& dv) const throw() {
00135     // Lee 54.21 but write (1 - dnu^2 * snv^2) = (cnv^2 + _mu * snu^2 * snv^2)
00136     // (see A+S 16.21.4)
00137     double d = _mv * sq(sq(cnv) + _mu * sq(snu * snv));
00138     du =  cnu * dnu * dnv * (sq(cnv) - _mu * sq(snu * snv)) / d;
00139     dv = -snu * snv * cnv * (sq(dnu * dnv) + _mu * sq(cnu)) / d;
00140   }
00141 
00142   // Starting point for zetainv
00143   bool TransverseMercatorExact::zetainv0(double psi, double lam,
00144                                           double& u, double& v) const throw() {
00145     bool retval = false;
00146     if (psi < -_e * Constants::pi()/4 &&
00147         lam > (1 - 2 * _e) * Constants::pi()/2 &&
00148         psi < lam - (1 - _e) * Constants::pi()/2) {
00149       // N.B. this branch is normally not taken because psi < 0 is converted
00150       // psi > 0 by Forward.
00151       //
00152       // There's a log singularity at w = w0 = Eu.K() + i * Ev.K(),
00153       // corresponding to the south pole, where we have, approximately
00154       //
00155       //   psi = _e + i * pi/2 - _e * atanh(cos(i * (w - w0)/(1 + _mu/2)))
00156       //
00157       // Inverting this gives:
00158       double
00159         psix = 1 - psi / _e,
00160         lamx = (Constants::pi()/2 - lam) / _e;
00161       u = asinh(sin(lamx) / hypot(cos(lamx), sinh(psix))) * (1 + _mu/2);
00162       v = atan2(cos(lamx), sinh(psix)) * (1 + _mu/2);
00163       u = _Eu.K() - u;
00164       v = _Ev.K() - v;
00165     } else if (psi < _e * Constants::pi()/2 &&
00166                lam > (1 - 2 * _e) * Constants::pi()/2) {
00167       // At w = w0 = i * Ev.K(), we have
00168       //
00169       //     zeta = zeta0 = i * (1 - _e) * pi/2
00170       //     zeta' = zeta'' = 0
00171       //
00172       // including the next term in the Taylor series gives:
00173       //
00174       // zeta = zeta0 - (_mv * _e) / 3 * (w - w0)^3
00175       //
00176       // When inverting this, we map arg(w - w0) = [-90, 0] to
00177       // arg(zeta - zeta0) = [-90, 180]
00178       double
00179         dlam = lam - (1 - _e) * Constants::pi()/2,
00180         rad = hypot(psi, dlam),
00181         // atan2(dlam-psi, psi+dlam) + 45d gives arg(zeta - zeta0) in range
00182         // [-135, 225).  Subtracting 180 (since multiplier is negative) makes
00183         // range [-315, 45).  Multiplying by 1/3 (for cube root) gives range
00184         // [-105, 15).  In particular the range [-90, 180] in zeta space maps
00185         // to [-90, 0] in w space as required.
00186         ang = atan2(dlam-psi, psi+dlam) - 0.75 * Constants::pi();
00187       // Error using this guess is about 0.21 * (rad/e)^(5/3)
00188       retval = rad < _e * taytol;
00189       rad = pow(3 / (_mv * _e) * rad, 1/3.0);
00190       ang /= 3;
00191       u = rad * cos(ang);
00192       v = rad * sin(ang) + _Ev.K();
00193     } else {
00194       // Use spherical TM, Lee 12.6 -- writing atanh(sin(lam) / cosh(psi)) =
00195       // asinh(sin(lam) / hypot(cos(lam), sinh(psi))).  This takes care of the
00196       // log singularity at zeta = Eu.K() (corresponding to the north pole)
00197       v = asinh(sin(lam) / hypot(cos(lam), sinh(psi)));
00198       u = atan2(sinh(psi), cos(lam));
00199       // But scale to put 90,0 on the right place
00200       u *= _Eu.K() / (Constants::pi()/2);
00201       v *= _Eu.K() / (Constants::pi()/2);
00202     }
00203     return retval;
00204   }
00205 
00206   // Invert zeta using Newton's method
00207   void  TransverseMercatorExact::zetainv(double psi, double lam,
00208                                          double& u, double& v) const throw() {
00209     if (zetainv0(psi, lam, u, v))
00210       return;
00211     double stol2 = tol2 / sq(max(psi, 1.0));
00212     // min iterations = 2, max iterations = 6; mean = 4.0
00213     for (int i = 0, trip = 0; i < numit; ++i) {
00214       double snu, cnu, dnu, snv, cnv, dnv;
00215       _Eu.sncndn(u, snu, cnu, dnu);
00216       _Ev.sncndn(v, snv, cnv, dnv);
00217       double psi1, lam1, du1, dv1;
00218       zeta(u, snu, cnu, dnu, v, snv, cnv, dnv, psi1, lam1);
00219       dwdzeta(u, snu, cnu, dnu, v, snv, cnv, dnv, du1, dv1);
00220       psi1 -= psi;
00221       lam1 -= lam;
00222       double
00223         delu = psi1 * du1 - lam1 * dv1,
00224         delv = psi1 * dv1 + lam1 * du1;
00225       u -= delu;
00226       v -= delv;
00227       if (trip)
00228         break;
00229       double delw2 = sq(delu) + sq(delv);
00230       if (delw2 < stol2)
00231         ++trip;
00232     }
00233   }
00234 
00235   void TransverseMercatorExact::sigma(double u,
00236                                       double snu, double cnu, double dnu,
00237                                       double v,
00238                                       double snv, double cnv, double dnv,
00239                                       double& xi, double& eta) const throw() {
00240     // Lee 55.4 writing
00241     // dnu^2 + dnv^2 - 1 = _mu * cnu^2 + _mv * cnv^2
00242     double d = _mu * sq(cnu) + _mv * sq(cnv);
00243     xi = _Eu.E(snu, cnu, dnu) - _mu * snu * cnu * dnu / d;
00244     eta = v - _Ev.E(snv, cnv, dnv) + _mv * snv * cnv * dnv / d;
00245   }
00246 
00247   void TransverseMercatorExact::dwdsigma(double u,
00248                                          double snu, double cnu, double dnu,
00249                                          double v,
00250                                          double snv, double cnv, double dnv,
00251                                          double& du, double& dv) const throw() {
00252     // Reciprocal of 55.9: dw/ds = dn(w)^2/_mv, expanding complex dn(w) using
00253     // A+S 16.21.4
00254     double d = _mv * sq(sq(cnv) + _mu * sq(snu * snv));
00255     double
00256       dnr = dnu * cnv * dnv,
00257       dni = - _mu * snu * cnu * snv;
00258     du = (sq(dnr) - sq(dni)) / d;
00259     dv = 2 * dnr * dni / d;
00260   }
00261 
00262   // Starting point for sigmainv
00263   bool  TransverseMercatorExact::sigmainv0(double xi, double eta,
00264                                            double& u, double& v) const throw() {
00265     bool retval = false;
00266     if (eta > 1.25 * _Ev.KE() ||
00267         (xi < -0.25 * _Eu.E() && xi < eta - _Ev.KE())) {
00268       // sigma as a simple pole at w = w0 = Eu.K() + i * Ev.K() and sigma is
00269       // approximated by
00270       //
00271       // sigma = (Eu.E() + i * Ev.KE()) + 1/(w - w0)
00272       double
00273         x = xi - _Eu.E(),
00274         y = eta - _Ev.KE(),
00275         r2 = sq(x) + sq(y);
00276       u = _Eu.K() + x/r2;
00277       v = _Ev.K() - y/r2;
00278     } else if ((eta > 0.75 * _Ev.KE() && xi < 0.25 * _Eu.E())
00279                || eta > _Ev.KE()) {
00280       // At w = w0 = i * Ev.K(), we have
00281       //
00282       //     sigma = sigma0 = i * Ev.KE()
00283       //     sigma' = sigma'' = 0
00284       //
00285       // including the next term in the Taylor series gives:
00286       //
00287       // sigma = sigma0 - _mv / 3 * (w - w0)^3
00288       //
00289       // When inverting this, we map arg(w - w0) = [-pi/2, -pi/6] to
00290       // arg(sigma - sigma0) = [-pi/2, pi/2]
00291       // mapping arg = [-pi/2, -pi/6] to [-pi/2, pi/2]
00292       double
00293         deta = eta - _Ev.KE(),
00294         rad = hypot(xi, deta),
00295         // Map the range [-90, 180] in sigma space to [-90, 0] in w space.  See
00296         // discussion in zetainv0 on the cut for ang.
00297         ang = atan2(deta-xi, xi+deta) - 0.75 * Constants::pi();
00298       // Error using this guess is about 0.068 * rad^(5/3)
00299       retval = rad < 2 * taytol;
00300       rad = pow(3 / _mv * rad, 1/3.0);
00301       ang /= 3;
00302       u = rad * cos(ang);
00303       v = rad * sin(ang) + _Ev.K();
00304     } else {
00305       // Else use w = sigma * Eu.K/Eu.E (which is correct in the limit _e -> 0)
00306       u = xi * _Eu.K()/_Eu.E();
00307       v = eta * _Eu.K()/_Eu.E();
00308     }
00309     return retval;
00310   }
00311 
00312   // Invert sigma using Newton's method
00313   void  TransverseMercatorExact::sigmainv(double xi, double eta,
00314                                           double& u, double& v) const throw() {
00315     if (sigmainv0(xi, eta, u, v))
00316       return;
00317     // min iterations = 2, max iterations = 7; mean = 3.9
00318     for (int i = 0, trip = 0; i < numit; ++i) {
00319       double snu, cnu, dnu, snv, cnv, dnv;
00320       _Eu.sncndn(u, snu, cnu, dnu);
00321       _Ev.sncndn(v, snv, cnv, dnv);
00322       double xi1, eta1, du1, dv1;
00323       sigma(u, snu, cnu, dnu, v, snv, cnv, dnv, xi1, eta1);
00324       dwdsigma(u, snu, cnu, dnu, v, snv, cnv, dnv, du1, dv1);
00325       xi1 -= xi;
00326       eta1 -= eta;
00327       double
00328         delu = xi1 * du1 - eta1 * dv1,
00329         delv = xi1 * dv1 + eta1 * du1;
00330       u -= delu;
00331       v -= delv;
00332       if (trip)
00333         break;
00334       double delw2 = sq(delu) + sq(delv);
00335       if (delw2 < tol2)
00336         ++trip;
00337     }
00338   }
00339 
00340   void TransverseMercatorExact::Scale(double phi, double lam,
00341                                       double snu, double cnu, double dnu,
00342                                       double snv, double cnv, double dnv,
00343                                       double& gamma, double& k) const throw() {
00344     double
00345       c = cos(phi),
00346       s = sin(lam),
00347       c2 = sq(c),
00348       s2 = sq(s),
00349       d = 1 - s2 * c2;
00350     // See comment after else clause for a discussion.
00351     if ( !( phi > 0 && d > 0.75 && c2 * sq(c2 * _ep2) * s2 < tol ) ) {
00352       // Lee 55.12 -- negated for our sign convention.  gamma gives the bearing
00353       // (clockwise from true north) of grid north
00354       gamma = atan2(_mv * snu * snv * cnv, cnu * dnu * dnv);
00355       // Lee 55.13 with nu given by Lee 9.1 -- in sqrt change the numerator
00356       // from
00357       //
00358       //    (1 - snu^2 * dnv^2) to (_mv * snv^2 + cnu^2 * dnv^2)
00359       //
00360       // to maintain accuracy near phi = 90 and change the denomintor from
00361       //
00362       //    (dnu^2 + dnv^2 - 1) to (_mu * cnu^2 + _mv * cnv^2)
00363       //
00364       // to maintain accuracy near phi = 0, lam = 90 * (1 - e).  Similarly
00365       // rewrite sqrt term in 9.1 as
00366       //
00367       //    _mv + _mu * c^2 instead of 1 - _mu * sin(phi)^2
00368       k = sqrt(_mv + _mu * sq(c)) / c *
00369         sqrt( (_mv * sq(snv) + sq(cnu * dnv)) /
00370               (_mu * sq(cnu) + _mv * sq(cnv)) );
00371     } else {           //  phi > 0 && d > 0.75 && c2 * sq(c2 * _ep2) * s2 < tol
00372       // Use series approximations for gamma and k accurate to ep2.  Ignored
00373       // O(ep2^2) are shown here as gam2 and k2.  So only use these series if
00374       // the larger O(ep2^2) term (gam2) is less that tol.
00375       double
00376         // gam2 = c2^3*s2*((4*c2^2-c2)*s2^2+(8-9*c2)*s2-2)/(3*d^3)*ep2^2
00377         gam1 = sq(c2) * s2 / d * _ep2,
00378         // k2 = c2^3*s2^2*((3*c2^3+12*c2^2-28*c2)*s2^2+
00379         // (-34*c2^2+124*c2-64)*s2-61*c2+48)/(24*d^4)*ep2^2
00380         k1 = gam1 * ((c2 - 2) * s2 + 1) / (2 * d);
00381       gamma = atan2(sin(phi) * s * (1 + gam1), cos(lam));
00382       k = (1 + k1) / sqrt(d);
00383     }
00384   }
00385 
00386   void TransverseMercatorExact::Forward(double lon0, double lat, double lon,
00387                                         double& x, double& y,
00388                                         double& gamma, double& k)
00389     const throw() {
00390     // Avoid losing a bit of accuracy in lon (assuming lon0 is an integer)
00391     if (lon - lon0 > 180)
00392       lon -= lon0 - 360;
00393     else if (lon - lon0 <= -180)
00394       lon -= lon0 + 360;
00395     else
00396       lon -= lon0;
00397     // Now lon in (-180, 180]
00398     // Explicitly enforce the parity
00399     int
00400       latsign = !_extendp && lat < 0 ? -1 : 1,
00401       lonsign = !_extendp && lon < 0 ? -1 : 1;
00402     lon *= lonsign;
00403     lat *= latsign;
00404     bool backside = !_extendp && lon > 90;
00405     if (backside) {
00406       if (lat == 0)
00407         latsign = -1;
00408       lon = 180 - lon;
00409     }
00410     double
00411       phi = lat * Constants::degree(),
00412       lam = lon * Constants::degree();
00413 
00414     // u,v = coordinates for the Thompson TM, Lee 54
00415     double u, v;
00416     if (lat == 90) {
00417       u = _Eu.K();
00418       v = 0;
00419     } else if (lat == 0 && lon == 90 * (1 - _e)) {
00420       u = 0;
00421       v = _Ev.K();
00422     } else
00423       zetainv(psi(phi), lam, u, v);
00424 
00425     double snu, cnu, dnu, snv, cnv, dnv;
00426     _Eu.sncndn(u, snu, cnu, dnu);
00427     _Ev.sncndn(v, snv, cnv, dnv);
00428 
00429     double xi, eta;
00430     sigma(u, snu, cnu, dnu, v, snv, cnv, dnv, xi, eta);
00431     if (backside)
00432       xi = 2 * _Eu.E() - xi;
00433     y = xi * _a * _k0 * latsign;
00434     x = eta * _a * _k0 * lonsign;
00435 
00436     Scale(phi, lam, snu, cnu, dnu, snv, cnv, dnv, gamma, k);
00437     gamma /= Constants::degree();
00438     if (backside)
00439       gamma = 180 - gamma;
00440     gamma *= latsign * lonsign;
00441     k *= _k0;
00442   }
00443 
00444   void TransverseMercatorExact::Reverse(double lon0, double x, double y,
00445                                         double& lat, double& lon,
00446                                         double& gamma, double& k)
00447     const throw() {
00448     // This undoes the steps in Forward.
00449     double
00450       xi = y / (_a * _k0),
00451       eta = x / (_a * _k0);
00452     // Explicitly enforce the parity
00453     int
00454       latsign = !_extendp && y < 0 ? -1 : 1,
00455       lonsign = !_extendp && x < 0 ? -1 : 1;
00456     xi *= latsign;
00457     eta *= lonsign;
00458     bool backside = !_extendp && xi > _Eu.E();
00459     if (backside)
00460       xi = 2 * _Eu.E()- xi;
00461 
00462     // u,v = coordinates for the Thompson TM, Lee 54
00463     double u, v;
00464     if (xi == 0 && eta == _Ev.KE()) {
00465       u = 0;
00466       v = _Ev.K();
00467     } else
00468       sigmainv(xi, eta, u, v);
00469 
00470     double snu, cnu, dnu, snv, cnv, dnv;
00471     _Eu.sncndn(u, snu, cnu, dnu);
00472     _Ev.sncndn(v, snv, cnv, dnv);
00473     double phi, lam;
00474     if (v != 0 || u != _Eu.K()) {
00475       double psi;
00476       zeta(u, snu, cnu, dnu, v, snv, cnv, dnv, psi, lam);
00477       phi = psiinv(psi);
00478       lat = phi / Constants::degree();
00479       lon = lam / Constants::degree();
00480     } else {
00481       phi = Constants::pi()/2;
00482       lat = 90;
00483       lon = lam = 0;
00484     }
00485     Scale(phi, lam, snu, cnu, dnu, snv, cnv, dnv, gamma, k);
00486     gamma /= Constants::degree();
00487     if (backside)
00488       lon = 180 - lon;
00489     lon *= lonsign;
00490     // Avoid losing a bit of accuracy in lon (assuming lon0 is an integer)
00491     if (lon + lon0 >= 180)
00492       lon += lon0 - 360;
00493     else if (lon + lon0 < -180)
00494       lon += lon0 + 360;
00495     else
00496       lon += lon0;
00497     lat *= latsign;
00498     if (backside)
00499       y = 2 * _Eu.E() - y;
00500     y *= _a * _k0 * latsign;
00501     x *= _a * _k0 * lonsign;
00502     if (backside)
00503       gamma = 180 - gamma;
00504     gamma *= latsign * lonsign;
00505     k *= _k0;
00506   }
00507 
00508 } // namespace GeographicLib