Geocentric.cpp
Go to the documentation of this file.
00001 /**
00002  * \file Geocentric.cpp
00003  * \brief Implementation for GeographicLib::Geocentric class
00004  *
00005  * Copyright (c) Charles Karney (2008) <charles@karney.com>
00006  * and licensed under the LGPL.
00007  **********************************************************************/
00008 
00009 #include "GeographicLib/Geocentric.hpp"
00010 #include "GeographicLib/Constants.hpp"
00011 #include <algorithm>
00012 #include <limits>
00013 
00014 namespace {
00015   char RCSID[] = "$Id: Geocentric.cpp 6568 2009-03-01 17:58:41Z ckarney $";
00016   char RCSID_H[] = GEOCENTRIC_HPP;
00017 }
00018 
00019 namespace GeographicLib {
00020 
00021   using namespace std;
00022 
00023   Geocentric::Geocentric(double a, double invf)
00024     throw()
00025     : _a(a)
00026     , _f(invf > 0 ? 1 / invf : 0)
00027     , _e2(_f * (2 - _f))
00028     , _e4(sq(_e2))
00029     , _e2m(1 - _e2)
00030     , _maxrad(2 * _a / numeric_limits<double>::epsilon())
00031   {}
00032 
00033   const Geocentric Geocentric::WGS84(Constants::WGS84_a(),
00034                                      Constants::WGS84_invf());
00035 
00036   void Geocentric::Forward(double lat, double lon, double h,
00037                            double& x, double& y, double& z) const throw() {
00038     double
00039       phi = lat * Constants::degree(),
00040       lam = lon * Constants::degree(),
00041       sphi = sin(phi),
00042       n = _a/sqrt(1 - _e2 * sq(sphi));
00043     z = ( sq(1 - _f) * n + h) * sphi;
00044     x = (n + h) * cos(phi);
00045     y = x * sin(lam);
00046     x *= cos(lam);
00047   }
00048 
00049   void Geocentric::Reverse(double x, double y, double z,
00050                            double& lat, double& lon, double& h) const throw() {
00051     double rad = hypot(x, y);
00052     h = hypot(rad, z);          // Distance to center of earth
00053     double phi;
00054     if (h > _maxrad)
00055       // We really far away (> 12 million light years); treat the earth as a
00056       // point and h, above, is an acceptable approximation to the height.
00057       // This avoids overflow, e.g., in the computation of disc below.  It's
00058       // possible that h has overflowed to inf; but that's OK.
00059       //
00060       // Treat the case x, y finite, but rad overflows to +inf by scaling by 2.
00061       phi = atan2(z/2, hypot(x/2, y/2));
00062     else if (_e4 == 0) {
00063       // Treat the spherical case.  Dealing with underflow in the general case
00064       // with _e2 = 0 is difficult.  Origin maps to N pole same as an
00065       // ellipsoid.
00066       phi = atan2(h != 0 ? z : 1.0, rad);
00067       h -= _a;
00068     } else {
00069       double
00070         p = sq(rad / _a),
00071         q = _e2m * sq(z / _a),
00072         r = (p + q - _e4) / 6;
00073       if ( !(_e4 * q == 0 && r <= 0) ) {
00074         double
00075           // Avoid possible division by zero when r = 0 by multiplying
00076           // equations for s and t by r^3 and r, resp.
00077           S = _e4 * p * q / 4,  // S = r^3 * s
00078           r2 = sq(r),
00079           r3 = r * r2,
00080           disc =  S * (2 * r3 + S);
00081         double u = r;
00082         if (disc >= 0) {
00083           double T3 = r3 + S;
00084           // Pick the sign on the sqrt to maximize abs(T3).  This minimizes
00085           // loss of precision due to cancellation.  The result is unchanged
00086           // because of the way the T is used in definition of u.
00087           T3 += T3 < 0 ? -sqrt(disc) : sqrt(disc); // T3 = (r * t)^3
00088           // N.B. cbrt always returns the real root.  cbrt(-8) = -2.
00089           double T = cbrt(T3);  // T = r * t
00090           // T can be zero; but then r2 / T -> 0.
00091           u += T + (T != 0 ? r2 / T : 0);
00092         } else {
00093           // T is complex, but the way u is defined the result is real.
00094           double ang = atan2(sqrt(-disc), r3 + S);
00095           // There are three possible real solutions for u depending on the
00096           // multiple of 2*pi here.  We choose multiplier = 1 which leads to a
00097           // jump in the solution across the line 2 + s = 0; but this
00098           // nevertheless leads to a continuous (and accurate) solution for k.
00099           // Other choices of the multiplier lead to poorly conditioned
00100           // solutions near s = 0 (i.e., near p = 0 or q = 0).
00101           u += 2 * abs(r) * cos((2 * Constants::pi() + ang) / 3.0);
00102         }
00103         double
00104           v = sqrt(sq(u) + _e4 * q), // guaranteed positive
00105           // Avoid loss of accuracy when u < 0.  Underflow doesn't occur in
00106           // e4 * q / (v - u) because u ~ e^4 when q is small and u < 0.
00107           uv = u < 0 ? _e4 * q / (v - u) : u + v, //  u+v, guaranteed positive
00108           // Need to guard against w going negative due to roundoff in uv - q.
00109           w = max(0.0, _e2 * (uv - q) / (2 * v)),
00110           // Rearrange expression for k to avoid loss of accuracy due to
00111           // subtraction.  Division by 0 not possible because uv > 0, w >= 0.
00112           k = uv / (sqrt(uv + sq(w)) + w), // guaranteed positive
00113           d = k * rad / (k + _e2);
00114         // Probably atan2 returns the result for phi more accurately than the
00115         // half-angle formula that Vermeille uses.  It's certainly simpler.
00116         phi = atan2(z, d);
00117         h = (k + _e2 - 1) * hypot(d, z) / k;
00118       } else {                  // e4 * q == 0 && r <= 0
00119         // Very near equatorial plane with rad <= a * e^2.  This leads to k = 0
00120         // using the general formula and division by 0 in formula for h.  So
00121         // handle this case directly.  The condition e4 * q == 0 implies abs(z)
00122         // < 1.e-145 for WGS84 so it's OK to treat these points as though z =
00123         // 0.  (But we do take care that the sign of phi matches the sign of
00124         // z.)
00125         phi = atan2(sqrt( -6 * r), sqrt(p * _e2m));
00126         if (z < 0) phi = -phi;  // for tiny negative z
00127         h = - _a * _e2m / sqrt(1 - _e2 * sq(sin(phi)));
00128       }
00129     }
00130     lat = phi / Constants::degree();
00131     // Negative signs return lon in [-180, 180).  Assume atan2(0,0) = 0.
00132     lon = -atan2(-y, x) / Constants::degree();
00133   }
00134 
00135 } // namespace GeographicLib
00136