#include <GeographicLib/NormalGravity.hpp>
namespace GeographicLib {
using namespace std;
void NormalGravity::Initialize(real a, real GM, real omega, real f_J2,
bool geometricp) {
_a = a;
if (!(isfinite(_a) && _a > 0))
throw GeographicErr("Equatorial radius is not positive");
_gGM = GM;
if (!isfinite(_gGM))
throw GeographicErr("Gravitational constant is not finite");
_omega = omega;
_omega2 = Math::sq(_omega);
_aomega2 = Math::sq(_omega * _a);
if (!(isfinite(_omega2) && isfinite(_aomega2)))
throw GeographicErr("Rotation velocity is not finite");
_f = geometricp ? f_J2 : J2ToFlattening(_a, _gGM, _omega, f_J2);
_b = _a * (1 - _f);
if (!(isfinite(_b) && _b > 0))
throw GeographicErr("Polar semi-axis is not positive");
_jJ2 = geometricp ? FlatteningToJ2(_a, _gGM, _omega, f_J2) : f_J2;
_e2 = _f * (2 - _f);
_ep2 = _e2 / (1 - _e2);
real ex2 = _f < 0 ? -_e2 : _ep2;
_qQ0 = Qf(ex2, _f < 0);
_earth = Geocentric(_a, _f);
_eE = _a * sqrt(fabs(_e2)); _uU0 = _gGM * atanzz(ex2, _f < 0) / _b + _aomega2 / 3;
real P = Hf(ex2, _f < 0) / (6 * _qQ0);
_gammae = _gGM / (_a * _b) - (1 + P) * _a * _omega2;
_gammap = _gGM / (_a * _a) + 2 * P * _b * _omega2;
_k = -_e2 * _gGM / (_a * _b) +
_omega2 * (P * (_a + 2 * _b * (1 - _f)) + _a);
_fstar = (-_f * _gGM / (_a * _b) + _omega2 * (P * (_a + 2 * _b) + _a)) /
_gammae;
}
NormalGravity::NormalGravity(real a, real GM, real omega, real f_J2,
bool geometricp) {
Initialize(a, GM, omega, f_J2, geometricp);
}
const NormalGravity& NormalGravity::WGS84() {
static const NormalGravity wgs84(Constants::WGS84_a(),
Constants::WGS84_GM(),
Constants::WGS84_omega(),
Constants::WGS84_f(), true);
return wgs84;
}
const NormalGravity& NormalGravity::GRS80() {
static const NormalGravity grs80(Constants::GRS80_a(),
Constants::GRS80_GM(),
Constants::GRS80_omega(),
Constants::GRS80_J2(), false);
return grs80;
}
Math::real NormalGravity::atan7series(real x) {
static const real lg2eps_ = -log2(numeric_limits<real>::epsilon() / 2);
int e;
(void) frexp(x, &e);
e = max(-e, 1); int n = x == 0 ? 1 : int(ceil(lg2eps_ / e));
Math::real v = 0;
while (n--) v = - x * v - 1/Math::real(2*n + 7);
return v;
}
Math::real NormalGravity::atan5series(real x) {
return 1/real(5) + x * atan7series(x);
}
Math::real NormalGravity::Qf(real x, bool alt) {
real y = alt ? -x / (1 + x) : x;
return !(4 * fabs(y) < 1) ? ((1 + 3/y) * atanzz(x, alt) - 3/y) / (2 * y) :
(3 * (3 + y) * atan5series(y) - 1) / 6;
}
Math::real NormalGravity::Hf(real x, bool alt) {
real y = alt ? -x / (1 + x) : x;
return !(4 * fabs(y) < 1) ? (3 * (1 + 1/y) * (1 - atanzz(x, alt)) - 1) / y :
1 - 3 * (1 + y) * atan5series(y);
}
Math::real NormalGravity::QH3f(real x, bool alt) {
real y = alt ? -x / (1 + x) : x;
return !(4 * fabs(y) < 1) ? ((9 + 15/y) * atanzz(x, alt) - 4 - 15/y) / (6 * Math::sq(y)) :
((25 + 15*y) * atan7series(y) + 3)/10;
}
Math::real NormalGravity::Jn(int n) const {
if (n & 1 || n < 0)
return 0;
n /= 2;
real e2n = 1; for (int j = n; j--;)
e2n *= -_e2;
return -3 * e2n * ((1 - n) + 5 * n * _jJ2 / _e2) / ((2 * n + 1) * (2 * n + 3));
}
Math::real NormalGravity::SurfaceGravity(real lat) const {
real sphi = Math::sind(Math::LatFix(lat));
return (_gammae + _k * Math::sq(sphi)) / sqrt(1 - _e2 * Math::sq(sphi));
}
Math::real NormalGravity::V0(real X, real Y, real Z,
real& GammaX, real& GammaY, real& GammaZ) const
{
real
p = hypot(X, Y),
clam = p != 0 ? X/p : 1,
slam = p != 0 ? Y/p : 0,
r = hypot(p, Z);
if (_f < 0) swap(p, Z);
real
Q = Math::sq(r) - Math::sq(_eE),
t2 = Math::sq(2 * _eE * Z),
disc = sqrt(Math::sq(Q) + t2),
u = sqrt((Q >= 0 ? (Q + disc) : t2 / (disc - Q)) / 2),
uE = hypot(u, _eE),
sbet = u != 0 ? Z * uE : copysign(sqrt(-Q), Z),
cbet = u != 0 ? p * u : p,
s = hypot(cbet, sbet);
sbet = s != 0 ? sbet/s : 1;
cbet = s != 0 ? cbet/s : 0;
real
z = _eE/u,
z2 = Math::sq(z),
den = hypot(u, _eE * sbet);
if (_f < 0) {
swap(sbet, cbet);
swap(u, uE);
}
real
invw = uE / den, bu = _b / (u != 0 || _f < 0 ? u : _eE),
q = ((u != 0 || _f < 0 ? Qf(z2, _f < 0) : Math::pi() / 4) / _qQ0) *
bu * Math::sq(bu),
qp = _b * Math::sq(bu) * (u != 0 || _f < 0 ? Hf(z2, _f < 0) : 2) / _qQ0,
ang = (Math::sq(sbet) - 1/real(3)) / 2,
Vres = _gGM * (u != 0 || _f < 0 ?
atanzz(z2, _f < 0) / u :
Math::pi() / (2 * _eE)) + _aomega2 * q * ang,
gamu = - (_gGM + (_aomega2 * qp * ang)) * invw / Math::sq(uE),
gamb = _aomega2 * q * sbet * cbet * invw / uE,
t = u * invw / uE,
gamp = t * cbet * gamu - invw * sbet * gamb;
GammaX = gamp * clam;
GammaY = gamp * slam;
GammaZ = invw * sbet * gamu + t * cbet * gamb;
return Vres;
}
Math::real NormalGravity::Phi(real X, real Y, real& fX, real& fY) const {
fX = _omega2 * X;
fY = _omega2 * Y;
return _omega2 * (Math::sq(X) + Math::sq(Y)) / 2;
}
Math::real NormalGravity::U(real X, real Y, real Z,
real& gammaX, real& gammaY, real& gammaZ) const {
real fX, fY;
real Ures = V0(X, Y, Z, gammaX, gammaY, gammaZ) + Phi(X, Y, fX, fY);
gammaX += fX;
gammaY += fY;
return Ures;
}
Math::real NormalGravity::Gravity(real lat, real h,
real& gammay, real& gammaz) const {
real X, Y, Z;
real M[Geocentric::dim2_];
_earth.IntForward(lat, 0, h, X, Y, Z, M);
real gammaX, gammaY, gammaZ,
Ures = U(X, Y, Z, gammaX, gammaY, gammaZ);
gammay = M[1] * gammaX + M[4] * gammaY + M[7] * gammaZ;
gammaz = M[2] * gammaX + M[5] * gammaY + M[8] * gammaZ;
return Ures;
}
Math::real NormalGravity::J2ToFlattening(real a, real GM,
real omega, real J2) {
static const real maxe_ = 1 - numeric_limits<real>::epsilon();
static const real eps2_ = sqrt(numeric_limits<real>::epsilon()) / 100;
real
K = 2 * Math::sq(a * omega) * a / (15 * GM),
J0 = (1 - 4 * K / Math::pi()) / 3;
if (!(GM > 0 && isfinite(K) && K >= 0))
return Math::NaN();
if (!(isfinite(J2) && J2 <= J0)) return Math::NaN();
if (J2 == J0) return 1;
real
ep2 = fmax(Math::sq(32 * K / (3 * Math::sq(Math::pi()) * (J0 - J2))),
-maxe_),
e2 = fmin(ep2 / (1 + ep2), maxe_);
for (int j = 0;
j < maxit_ ||
GEOGRAPHICLIB_PANIC("Convergence failure in NormalGravity");
++j) {
real
e2a = e2, ep2a = ep2,
f2 = 1 - e2, f1 = sqrt(f2), Q0 = Qf(e2 < 0 ? -e2 : ep2, e2 < 0),
h = e2 - f1 * f2 * K / Q0 - 3 * J2,
dh = 1 - 3 * f1 * K * QH3f(e2 < 0 ? -e2 : ep2, e2 < 0) /
(2 * Math::sq(Q0));
e2 = fmin(e2a - h / dh, maxe_);
ep2 = fmax(e2 / (1 - e2), -maxe_);
if (fabs(h) < eps2_ || e2 == e2a || ep2 == ep2a)
break;
}
return e2 / (1 + sqrt(1 - e2));
}
Math::real NormalGravity::FlatteningToJ2(real a, real GM,
real omega, real f) {
real
K = 2 * Math::sq(a * omega) * a / (15 * GM),
f1 = 1 - f,
f2 = Math::sq(f1),
e2 = f * (2 - f);
return (e2 - K * f1 * f2 / Qf(f < 0 ? -e2 : e2 / f2, f < 0)) / 3;
}
}