#include <GeographicLib/LambertConformalConic.hpp>
namespace GeographicLib {
using namespace std;
LambertConformalConic::LambertConformalConic(real a, real f,
real stdlat, real k0)
: eps_(numeric_limits<real>::epsilon())
, epsx_(Math::sq(eps_))
, ahypover_(Math::digits() * log(real(numeric_limits<real>::radix)) + 2)
, _a(a)
, _f(f)
, _fm(1 - _f)
, _e2(_f * (2 - _f))
, _es((_f < 0 ? -1 : 1) * sqrt(fabs(_e2)))
{
if (!(isfinite(_a) && _a > 0))
throw GeographicErr("Equatorial radius is not positive");
if (!(isfinite(_f) && _f < 1))
throw GeographicErr("Polar semi-axis is not positive");
if (!(isfinite(k0) && k0 > 0))
throw GeographicErr("Scale is not positive");
if (!(fabs(stdlat) <= Math::qd))
throw GeographicErr("Standard latitude not in [-" + to_string(Math::qd)
+ "d, " + to_string(Math::qd) + "d]");
real sphi, cphi;
Math::sincosd(stdlat, sphi, cphi);
Init(sphi, cphi, sphi, cphi, k0);
}
LambertConformalConic::LambertConformalConic(real a, real f,
real stdlat1, real stdlat2,
real k1)
: eps_(numeric_limits<real>::epsilon())
, epsx_(Math::sq(eps_))
, ahypover_(Math::digits() * log(real(numeric_limits<real>::radix)) + 2)
, _a(a)
, _f(f)
, _fm(1 - _f)
, _e2(_f * (2 - _f))
, _es((_f < 0 ? -1 : 1) * sqrt(fabs(_e2)))
{
if (!(isfinite(_a) && _a > 0))
throw GeographicErr("Equatorial radius is not positive");
if (!(isfinite(_f) && _f < 1))
throw GeographicErr("Polar semi-axis is not positive");
if (!(isfinite(k1) && k1 > 0))
throw GeographicErr("Scale is not positive");
if (!(fabs(stdlat1) <= Math::qd))
throw GeographicErr("Standard latitude 1 not in [-"
+ to_string(Math::qd) + "d, "
+ to_string(Math::qd) + "d]");
if (!(fabs(stdlat2) <= Math::qd))
throw GeographicErr("Standard latitude 2 not in [-"
+ to_string(Math::qd) + "d, "
+ to_string(Math::qd) + "d]");
real sphi1, cphi1, sphi2, cphi2;
Math::sincosd(stdlat1, sphi1, cphi1);
Math::sincosd(stdlat2, sphi2, cphi2);
Init(sphi1, cphi1, sphi2, cphi2, k1);
}
LambertConformalConic::LambertConformalConic(real a, real f,
real sinlat1, real coslat1,
real sinlat2, real coslat2,
real k1)
: eps_(numeric_limits<real>::epsilon())
, epsx_(Math::sq(eps_))
, ahypover_(Math::digits() * log(real(numeric_limits<real>::radix)) + 2)
, _a(a)
, _f(f)
, _fm(1 - _f)
, _e2(_f * (2 - _f))
, _es((_f < 0 ? -1 : 1) * sqrt(fabs(_e2)))
{
if (!(isfinite(_a) && _a > 0))
throw GeographicErr("Equatorial radius is not positive");
if (!(isfinite(_f) && _f < 1))
throw GeographicErr("Polar semi-axis is not positive");
if (!(isfinite(k1) && k1 > 0))
throw GeographicErr("Scale is not positive");
if (signbit(coslat1))
throw GeographicErr("Standard latitude 1 not in [-"
+ to_string(Math::qd) + "d, "
+ to_string(Math::qd) + "d]");
if (signbit(coslat2))
throw GeographicErr("Standard latitude 2 not in [-"
+ to_string(Math::qd) + "d, "
+ to_string(Math::qd) + "d]");
if (!(fabs(sinlat1) <= 1 && coslat1 <= 1) || (coslat1 == 0 && sinlat1 == 0))
throw GeographicErr("Bad sine/cosine of standard latitude 1");
if (!(fabs(sinlat2) <= 1 && coslat2 <= 1) || (coslat2 == 0 && sinlat2 == 0))
throw GeographicErr("Bad sine/cosine of standard latitude 2");
if (coslat1 == 0 || coslat2 == 0)
if (!(coslat1 == coslat2 && sinlat1 == sinlat2))
throw GeographicErr
("Standard latitudes must be equal is either is a pole");
Init(sinlat1, coslat1, sinlat2, coslat2, k1);
}
void LambertConformalConic::Init(real sphi1, real cphi1,
real sphi2, real cphi2, real k1) {
{
real r;
r = hypot(sphi1, cphi1);
sphi1 /= r; cphi1 /= r;
r = hypot(sphi2, cphi2);
sphi2 /= r; cphi2 /= r;
}
bool polar = (cphi1 == 0);
cphi1 = fmax(epsx_, cphi1); cphi2 = fmax(epsx_, cphi2);
_sign = sphi1 + sphi2 >= 0 ? 1 : -1;
sphi1 *= _sign; sphi2 *= _sign;
if (sphi1 > sphi2) {
swap(sphi1, sphi2); swap(cphi1, cphi2); }
real
tphi1 = sphi1/cphi1, tphi2 = sphi2/cphi2, tphi0;
real
tbet1 = _fm * tphi1, scbet1 = hyp(tbet1),
tbet2 = _fm * tphi2, scbet2 = hyp(tbet2);
real
scphi1 = 1/cphi1,
xi1 = Math::eatanhe(sphi1, _es), shxi1 = sinh(xi1), chxi1 = hyp(shxi1),
tchi1 = chxi1 * tphi1 - shxi1 * scphi1, scchi1 = hyp(tchi1),
scphi2 = 1/cphi2,
xi2 = Math::eatanhe(sphi2, _es), shxi2 = sinh(xi2), chxi2 = hyp(shxi2),
tchi2 = chxi2 * tphi2 - shxi2 * scphi2, scchi2 = hyp(tchi2),
psi1 = asinh(tchi1);
if (tphi2 - tphi1 != 0) {
real num = Dlog1p(Math::sq(tbet2)/(1 + scbet2),
Math::sq(tbet1)/(1 + scbet1))
* Dhyp(tbet2, tbet1, scbet2, scbet1) * _fm;
real den = Dasinh(tphi2, tphi1, scphi2, scphi1)
- Deatanhe(sphi2, sphi1) * Dsn(tphi2, tphi1, sphi2, sphi1);
_n = num/den;
if (_n < 1/real(4))
_nc = sqrt((1 - _n) * (1 + _n));
else {
real t;
{
real
s1 = (tphi1 * (2 * shxi1 * chxi1 * scphi1 - _e2 * tphi1) -
Math::sq(shxi1) * (1 + 2 * Math::sq(tphi1))),
s2 = (tphi2 * (2 * shxi2 * chxi2 * scphi2 - _e2 * tphi2) -
Math::sq(shxi2) * (1 + 2 * Math::sq(tphi2))),
t1 = tchi1 < 0 ? scbet1 - tchi1 : (s1 + 1)/(scbet1 + tchi1),
t2 = tchi2 < 0 ? scbet2 - tchi2 : (s2 + 1)/(scbet2 + tchi2),
a2 = -(s2 / (scbet2 + scchi2) + t2) / (2 * scbet2),
a1 = -(s1 / (scbet1 + scchi1) + t1) / (2 * scbet1);
t = Dlog1p(a2, a1) / den;
}
t *= ( ( (tchi2 >= 0 ? scchi2 + tchi2 : 1/(scchi2 - tchi2)) +
(tchi1 >= 0 ? scchi1 + tchi1 : 1/(scchi1 - tchi1)) ) /
(4 * scbet1 * scbet2) ) * _fm;
real tbm = ( ((tbet1 > 0 ? 1/(scbet1+tbet1) : scbet1 - tbet1) +
(tbet2 > 0 ? 1/(scbet2+tbet2) : scbet2 - tbet2)) /
(scbet1+scbet2) );
real
dtchi = den / Dasinh(tchi2, tchi1, scchi2, scchi1),
dbet = (_e2/_fm) * ( 1 / (scbet2 + _fm * scphi2) +
1 / (scbet1 + _fm * scphi1) );
real
xiZ = Math::eatanhe(real(1), _es),
shxiZ = sinh(xiZ), chxiZ = hyp(shxiZ),
dxiZ1 = Deatanhe(real(1), sphi1)/(scphi1*(tphi1+scphi1)),
dxiZ2 = Deatanhe(real(1), sphi2)/(scphi2*(tphi2+scphi2)),
dshxiZ1 = Dsinh(xiZ, xi1, shxiZ, shxi1, chxiZ, chxi1) * dxiZ1,
dshxiZ2 = Dsinh(xiZ, xi2, shxiZ, shxi2, chxiZ, chxi2) * dxiZ2,
dchxiZ1 = Dhyp(shxiZ, shxi1, chxiZ, chxi1) * dshxiZ1,
dchxiZ2 = Dhyp(shxiZ, shxi2, chxiZ, chxi2) * dshxiZ2,
amu12 = (- scphi1 * dchxiZ1 + tphi1 * dshxiZ1
- scphi2 * dchxiZ2 + tphi2 * dshxiZ2),
dxi = Deatanhe(sphi1, sphi2) * Dsn(tphi2, tphi1, sphi2, sphi1),
dnu12 =
( (_f * 4 * scphi2 * dshxiZ2 > _f * scphi1 * dshxiZ1 ?
(dshxiZ1 + dshxiZ2)/2 * Dhyp(tphi1, tphi2, scphi1, scphi2)
- ( (scphi1 + scphi2)/2
* Dsinh(xi1, xi2, shxi1, shxi2, chxi1, chxi2) * dxi ) :
(scphi2 * dshxiZ2 - scphi1 * dshxiZ1)/(tphi2 - tphi1))
+ ( (tphi1 + tphi2)/2 * Dhyp(shxi1, shxi2, chxi1, chxi2)
* Dsinh(xi1, xi2, shxi1, shxi2, chxi1, chxi2) * dxi )
- (dchxiZ1 + dchxiZ2)/2 ),
dchia = (amu12 - dnu12 * (scphi2 + scphi1)),
tam = (dchia - dtchi * dbet) / (scchi1 + scchi2);
t *= tbm - tam;
_nc = sqrt(fmax(real(0), t) * (1 + _n));
}
{
real r = hypot(_n, _nc);
_n /= r;
_nc /= r;
}
tphi0 = _n / _nc;
} else {
tphi0 = tphi1;
_nc = 1/hyp(tphi0);
_n = tphi0 * _nc;
if (polar)
_nc = 0;
}
_scbet0 = hyp(_fm * tphi0);
real shxi0 = sinh(Math::eatanhe(_n, _es));
_tchi0 = tphi0 * hyp(shxi0) - shxi0 * hyp(tphi0); _scchi0 = hyp(_tchi0);
_psi0 = asinh(_tchi0);
_lat0 = atan(_sign * tphi0) / Math::degree();
_t0nm1 = expm1(- _n * _psi0); _scale = _a * k1 / scbet1 *
exp( - (Math::sq(_nc)/(1 + _n)) * psi1 )
* (tchi1 >= 0 ? scchi1 + tchi1 : 1 / (scchi1 - tchi1));
_k0 = k1 * (_scbet0/scbet1) *
exp( - (Math::sq(_nc)/(1 + _n)) *
Dasinh(tchi1, _tchi0, scchi1, _scchi0) * (tchi1 - _tchi0))
* (tchi1 >= 0 ? scchi1 + tchi1 : 1 / (scchi1 - tchi1)) /
(_scchi0 + _tchi0);
_nrho0 = polar ? 0 : _a * _k0 / _scbet0;
{
real
sphi = -1, cphi = epsx_,
tphi = sphi/cphi,
scphi = 1/cphi, shxi = sinh(Math::eatanhe(sphi, _es)),
tchi = hyp(shxi) * tphi - shxi * scphi, scchi = hyp(tchi),
psi = asinh(tchi),
dpsi = Dasinh(tchi, _tchi0, scchi, _scchi0) * (tchi - _tchi0);
_drhomax = - _scale * (2 * _nc < 1 && dpsi != 0 ?
(exp(Math::sq(_nc)/(1 + _n) * psi ) *
(tchi > 0 ? 1/(scchi + tchi) : (scchi - tchi))
- (_t0nm1 + 1))/(-_n) :
Dexp(-_n * psi, -_n * _psi0) * dpsi);
}
}
const LambertConformalConic& LambertConformalConic::Mercator() {
static const LambertConformalConic mercator(Constants::WGS84_a(),
Constants::WGS84_f(),
real(0), real(1));
return mercator;
}
void LambertConformalConic::Forward(real lon0, real lat, real lon,
real& x, real& y,
real& gamma, real& k) const {
lon = Math::AngDiff(lon0, lon);
real sphi, cphi;
Math::sincosd(Math::LatFix(lat) * _sign, sphi, cphi);
cphi = fmax(epsx_, cphi);
real
lam = lon * Math::degree(),
tphi = sphi/cphi, scbet = hyp(_fm * tphi),
scphi = 1/cphi, shxi = sinh(Math::eatanhe(sphi, _es)),
tchi = hyp(shxi) * tphi - shxi * scphi, scchi = hyp(tchi),
psi = asinh(tchi),
theta = _n * lam, stheta = sin(theta), ctheta = cos(theta),
dpsi = Dasinh(tchi, _tchi0, scchi, _scchi0) * (tchi - _tchi0),
drho = - _scale * (2 * _nc < 1 && dpsi != 0 ?
(exp(Math::sq(_nc)/(1 + _n) * psi ) *
(tchi > 0 ? 1/(scchi + tchi) : (scchi - tchi))
- (_t0nm1 + 1))/(-_n) :
Dexp(-_n * psi, -_n * _psi0) * dpsi);
x = (_nrho0 + _n * drho) * (_n != 0 ? stheta / _n : lam);
y = _nrho0 *
(_n != 0 ?
(ctheta < 0 ? 1 - ctheta : Math::sq(stheta)/(1 + ctheta)) / _n : 0)
- drho * ctheta;
k = _k0 * (scbet/_scbet0) /
(exp( - (Math::sq(_nc)/(1 + _n)) * dpsi )
* (tchi >= 0 ? scchi + tchi : 1 / (scchi - tchi)) / (_scchi0 + _tchi0));
y *= _sign;
gamma = _sign * theta / Math::degree();
}
void LambertConformalConic::Reverse(real lon0, real x, real y,
real& lat, real& lon,
real& gamma, real& k) const {
y *= _sign;
real
nx = _n * x, ny = _n != 0 ? _n * y : 0, y1 = _nrho0 - ny,
den = hypot(nx, y1) + _nrho0, drho = ((den != 0 && isfinite(den))
? (x*nx + y * (ny - 2*_nrho0)) / den
: den);
drho = fmin(drho, _drhomax);
if (_n == 0)
drho = fmax(drho, -_drhomax);
real
tnm1 = _t0nm1 + _n * drho/_scale,
dpsi = (den == 0 ? 0 :
(tnm1 + 1 != 0 ? - Dlog1p(tnm1, _t0nm1) * drho / _scale :
ahypover_));
real tchi;
if (2 * _n <= 1) {
real
psi = _psi0 + dpsi, tchia = sinh(psi), scchi = hyp(tchia),
dtchi = Dsinh(psi, _psi0, tchia, _tchi0, scchi, _scchi0) * dpsi;
tchi = _tchi0 + dtchi; } else {
real
tn = tnm1 + 1 == 0 ? epsx_ : tnm1 + 1,
sh = sinh( -Math::sq(_nc)/(_n * (1 + _n)) *
(2 * tn > 1 ? log1p(tnm1) : log(tn)) );
tchi = sh * (tn + 1/tn)/2 - hyp(sh) * (tnm1 * (tn + 1)/tn)/2;
}
gamma = atan2(nx, y1);
real
tphi = Math::tauf(tchi, _es),
scbet = hyp(_fm * tphi), scchi = hyp(tchi),
lam = _n != 0 ? gamma / _n : x / y1;
lat = Math::atand(_sign * tphi);
lon = lam / Math::degree();
lon = Math::AngNormalize(lon + Math::AngNormalize(lon0));
k = _k0 * (scbet/_scbet0) /
(exp(_nc != 0 ? - (Math::sq(_nc)/(1 + _n)) * dpsi : 0)
* (tchi >= 0 ? scchi + tchi : 1 / (scchi - tchi)) / (_scchi0 + _tchi0));
gamma /= _sign * Math::degree();
}
void LambertConformalConic::SetScale(real lat, real k) {
if (!(isfinite(k) && k > 0))
throw GeographicErr("Scale is not positive");
if (!(fabs(lat) <= Math::qd))
throw GeographicErr("Latitude for SetScale not in [-"
+ to_string(Math::qd) + "d, "
+ to_string(Math::qd) + "d]");
if (fabs(lat) == Math::qd && !(_nc == 0 && lat * _n > 0))
throw GeographicErr("Incompatible polar latitude in SetScale");
real x, y, gamma, kold;
Forward(0, lat, 0, x, y, gamma, kold);
k /= kold;
_scale *= k;
_k0 *= k;
}
}