#include <GeographicLib/Rhumb.hpp>
#include <GeographicLib/DST.hpp>
namespace GeographicLib {
using namespace std;
Rhumb::Rhumb(real a, real f, bool exact)
: _aux(a, f)
, _exact(exact)
, _a(a)
, _f(f)
, _n(_f / (2 - _f))
, _rm(_aux.RectifyingRadius(_exact))
, _c2(_aux.AuthalicRadiusSquared(_exact) * Math::degree())
, _lL(_exact ? 8 : Lmax_) , _pP(_lL)
{
AreaCoeffs();
}
const Rhumb& Rhumb::WGS84() {
static const Rhumb
wgs84(Constants::WGS84_a(), Constants::WGS84_f());
return wgs84;
}
void Rhumb::AreaCoeffs() {
if (_exact) {
static const real eps = numeric_limits<real>::epsilon()/2;
qIntegrand f(_aux);
int L = 4;
vector<real> c(L);
DST fft(L); fft.transform(f, c.data()); L *= 2;
int Lmax = 1<<(int(ceil(log2(max(Math::digits(), 64)))) + 6);
for (_lL = 0; L <= Lmax && _lL == 0; L *=2) {
fft.reset(L/2); c.resize(L); fft.refine(f, c.data());
_pP.resize(L);
for (int l = 0, k = -1; l < L; ++l) {
_pP[l] = (c[l] + (l+1 < L ? c[l+1] : 0)) / (-4 * (l+1));
if (fabs(_pP[l]) <= eps) {
if (k < 0) k = l; } else
k = -1; if (k >= 0 && l - k + 1 >= (l + 1 + 7) / 8) {
_lL = l + 1; _pP.resize(_lL); break;
}
}
}
if (_lL == 0) _lL = int(_pP.size());
} else {
#if GEOGRAPHICLIB_RHUMBAREA_ORDER == 4
static const real coeffs[] = {
596/real(2025), -398/real(945), 22/real(45), -1/real(3),
1543/real(4725), -118/real(315), 1/real(5),
152/real(945), -17/real(315),
5/real(252),
};
#elif GEOGRAPHICLIB_RHUMBAREA_ORDER == 5
static const real coeffs[] = {
-102614/real(467775), 596/real(2025), -398/real(945), 22/real(45),
-1/real(3),
-24562/real(155925), 1543/real(4725), -118/real(315), 1/real(5),
-38068/real(155925), 152/real(945), -17/real(315),
-752/real(10395), 5/real(252),
-101/real(17325),
};
#elif GEOGRAPHICLIB_RHUMBAREA_ORDER == 6
static const real coeffs[] = {
138734126/real(638512875), -102614/real(467775), 596/real(2025),
-398/real(945), 22/real(45), -1/real(3),
17749373/real(425675250), -24562/real(155925), 1543/real(4725),
-118/real(315), 1/real(5),
1882432/real(8513505), -38068/real(155925), 152/real(945),
-17/real(315),
268864/real(2027025), -752/real(10395), 5/real(252),
62464/real(2027025), -101/real(17325),
11537/real(4054050),
};
#elif GEOGRAPHICLIB_RHUMBAREA_ORDER == 7
static const real coeffs[] = {
-565017322/real(1915538625), 138734126/real(638512875),
-102614/real(467775), 596/real(2025), -398/real(945), 22/real(45),
-1/real(3),
-1969276/real(58046625), 17749373/real(425675250), -24562/real(155925),
1543/real(4725), -118/real(315), 1/real(5),
-58573784/real(638512875), 1882432/real(8513505), -38068/real(155925),
152/real(945), -17/real(315),
-6975184/real(42567525), 268864/real(2027025), -752/real(10395),
5/real(252),
-112832/real(1447875), 62464/real(2027025), -101/real(17325),
-4096/real(289575), 11537/real(4054050),
-311/real(525525),
};
#elif GEOGRAPHICLIB_RHUMBAREA_ORDER == 8
static const real coeffs[] = {
188270561816LL/real(488462349375LL), -565017322/real(1915538625),
138734126/real(638512875), -102614/real(467775), 596/real(2025),
-398/real(945), 22/real(45), -1/real(3),
2332829602LL/real(23260111875LL), -1969276/real(58046625),
17749373/real(425675250), -24562/real(155925), 1543/real(4725),
-118/real(315), 1/real(5),
-41570288/real(930404475), -58573784/real(638512875),
1882432/real(8513505), -38068/real(155925), 152/real(945),
-17/real(315),
1538774036/real(10854718875LL), -6975184/real(42567525),
268864/real(2027025), -752/real(10395), 5/real(252),
436821248/real(3618239625LL), -112832/real(1447875),
62464/real(2027025), -101/real(17325),
3059776/real(80405325), -4096/real(289575), 11537/real(4054050),
4193792/real(723647925), -311/real(525525),
1097653/real(1929727800),
};
#else
#error "Bad value for GEOGRAPHICLIB_RHUMBAREA_ORDER"
#endif
static_assert(sizeof(coeffs) / sizeof(real) ==
(Lmax_ * (Lmax_ + 1))/2,
"Coefficient array size mismatch for Rhumb");
real d = 1;
int o = 0;
for (int l = 0; l < Lmax_; ++l) {
int m = Lmax_ - l - 1;
d *= _n;
_pP[l] = d * Math::polyval(m, coeffs + o, _n);
o += m + 1;
}
}
}
Rhumb::qIntegrand::qIntegrand(const AuxLatitude& aux)
: _aux(aux) {}
Math::real Rhumb::qIntegrand::operator()(real beta) const {
AuxAngle betaa(AuxAngle::radians(beta)),
phia(_aux.Convert(AuxLatitude::BETA, AuxLatitude::PHI,
betaa, true).normalized()),
chia(_aux.Convert(AuxLatitude::PHI , AuxLatitude::CHI,
phia , true).normalized()),
xia (_aux.Convert(AuxLatitude::PHI , AuxLatitude::XI ,
phia , true).normalized());
real schi = chia.y(), cchi = chia.x(), sxi = xia.y(), cxi = xia.x(),
cphi = phia.x(), cbeta = betaa.x();
return (1 - _aux.Flattening()) *
( fabs(schi) < fabs(cchi) ? sxi - schi :
(cchi - cxi) * (cxi + cchi) / (sxi + schi) ) / (cphi * cbeta);
}
void Rhumb::GenInverse(real lat1, real lon1, real lat2, real lon2,
unsigned outmask,
real& s12, real& azi12, real& S12) const {
using std::isinf; AuxAngle phi1(AuxAngle::degrees(lat1)), phi2(AuxAngle::degrees(lat2)),
chi1(_aux.Convert(_aux.PHI, _aux.CHI, phi1, _exact)),
chi2(_aux.Convert(_aux.PHI, _aux.CHI, phi2, _exact));
real
lon12 = Math::AngDiff(lon1, lon2),
lam12 = lon12 * Math::degree<real>(),
psi1 = chi1.lam(),
psi2 = chi2.lam(),
psi12 = psi2 - psi1;
if (outmask & AZIMUTH)
azi12 = Math::atan2d(lam12, psi12);
if (outmask & DISTANCE) {
if (isinf(psi1) || isinf(psi2)) {
s12 = fabs(_aux.Convert(AuxLatitude::PHI, AuxLatitude::MU,
phi2, _exact).radians() -
_aux.Convert(AuxLatitude::PHI, AuxLatitude::MU,
phi1, _exact).radians()) * _rm;
} else {
real h = hypot(lam12, psi12);
real dmudpsi = _exact ?
_aux.DRectifying(phi1, phi2) / _aux.DIsometric(phi1, phi2) :
_aux.DConvert(AuxLatitude::CHI, AuxLatitude::MU, chi1, chi2)
/ DAuxLatitude::Dlam(chi1.tan(), chi2.tan());
s12 = h * dmudpsi * _rm;
}
}
if (outmask & AREA)
S12 = _c2 * lon12 * MeanSinXi(chi1, chi2);
}
RhumbLine Rhumb::Line(real lat1, real lon1, real azi12) const
{ return RhumbLine(*this, lat1, lon1, azi12); }
void Rhumb::GenDirect(real lat1, real lon1, real azi12, real s12,
unsigned outmask,
real& lat2, real& lon2, real& S12) const
{ Line(lat1, lon1, azi12).GenPosition(s12, outmask, lat2, lon2, S12); }
Math::real Rhumb::MeanSinXi(const AuxAngle& chix, const AuxAngle& chiy)
const {
AuxAngle
phix (_aux.Convert(_aux.CHI, _aux.PHI , chix, _exact)),
phiy (_aux.Convert(_aux.CHI, _aux.PHI , chiy, _exact)),
betax(_aux.Convert(_aux.PHI, _aux.BETA, phix, _exact).normalized()),
betay(_aux.Convert(_aux.PHI, _aux.BETA, phiy, _exact).normalized());
real DpbetaDbeta =
DAuxLatitude::DClenshaw(false,
betay.radians() - betax.radians(),
betax.y(), betax.x(), betay.y(), betay.x(),
_pP.data(), _lL),
tx = chix.tan(), ty = chiy.tan(),
DbetaDpsi = _exact ?
_aux.DParametric(phix, phiy) / _aux.DIsometric(phix, phiy) :
_aux.DConvert(AuxLatitude::CHI, AuxLatitude::BETA, chix, chiy) /
DAuxLatitude::Dlam(tx, ty);
return DAuxLatitude::Dp0Dpsi(tx, ty) + DpbetaDbeta * DbetaDpsi;
}
RhumbLine::RhumbLine(const Rhumb& rh, real lat1, real lon1, real azi12)
: _rh(rh)
, _lat1(Math::LatFix(lat1))
, _lon1(lon1)
, _azi12(Math::AngNormalize(azi12))
{
Math::sincosd(_azi12, _salp, _calp);
_phi1 = AuxAngle::degrees(lat1);
_mu1 = _rh._aux.Convert(AuxLatitude::PHI, AuxLatitude::MU,
_phi1, _rh._exact).degrees();
_chi1 = _rh._aux.Convert(AuxLatitude::PHI, AuxLatitude::CHI,
_phi1, _rh._exact);
_psi1 = _chi1.lam();
}
void RhumbLine::GenPosition(real s12, unsigned outmask,
real& lat2, real& lon2, real& S12) const {
real
r12 = s12 / (_rh._rm * Math::degree()), mu12 = r12 * _calp,
mu2 = _mu1 + mu12;
real lat2x, lon2x;
if (fabs(mu2) <= Math::qd) {
AuxAngle mu2a(AuxAngle::degrees(mu2)),
phi2(_rh._aux.Convert(AuxLatitude::MU, AuxLatitude::PHI,
mu2a, _rh._exact)),
chi2(_rh._aux.Convert(AuxLatitude::PHI, AuxLatitude::CHI,
phi2, _rh._exact));
lat2x = phi2.degrees();
real dmudpsi = _rh._exact ?
_rh._aux.DRectifying(_phi1, phi2) / _rh._aux.DIsometric(_phi1, phi2) :
_rh._aux.DConvert(AuxLatitude::CHI, AuxLatitude::MU, _chi1, chi2)
/ DAuxLatitude::Dlam(_chi1.tan(), chi2.tan());
lon2x = r12 * _salp / dmudpsi;
if (outmask & AREA)
S12 = _rh._c2 * lon2x * _rh.MeanSinXi(_chi1, chi2);
lon2x = outmask & LONG_UNROLL ? _lon1 + lon2x :
Math::AngNormalize(Math::AngNormalize(_lon1) + lon2x);
} else {
mu2 = Math::AngNormalize(mu2);
if (fabs(mu2) > Math::qd) mu2 = Math::AngNormalize(Math::hd - mu2);
lat2x = _rh._aux.Convert(AuxLatitude::MU, AuxLatitude::PHI,
AuxAngle::degrees(mu2), _rh._exact).degrees();
lon2x = Math::NaN();
if (outmask & AREA)
S12 = Math::NaN();
}
if (outmask & LATITUDE) lat2 = lat2x;
if (outmask & LONGITUDE) lon2 = lon2x;
}
}