use crate::ellps::{Ellipsoid, Shape};
use crate::errors::{Error, Result};
use crate::math::consts::{FRAC_PI_2, FRAC_PI_4};
use crate::parameters::ParamList;
use crate::proj::ProjData;
super::projection! { krovak }
const EPS: f64 = 1.0e-15;
const UQ: f64 = 1.04216856380474; const S0: f64 = 1.37008346281555;
const MAX_ITER: usize = 100;
#[derive(Debug, Clone)]
pub(crate) struct Projection {
e: f64,
xyfact: (f64, f64),
alpha: f64,
k: f64,
n: f64,
rho0: f64,
ad: f64,
easting_northing: bool,
}
impl Projection {
pub fn krovak(p: &mut ProjData, params: &ParamList) -> Result<Self> {
p.ellps = Ellipsoid::calc_ellipsoid_params(6377397.155, Shape::SP_es(0.006674372230614))?;
if params.get("lat_0").is_none() {
p.phi0 = 0.863937979737193;
}
if params.get("lon_0").is_none() {
p.lam0 = 0.7417649320975901 - 0.308341501185665;
}
if params.get("k").is_none() && params.get("k0").is_none() {
p.k0 = 0.9999;
}
let easting_northing = !params.check_option("czech")?;
let (e, es) = (p.ellps.e, p.ellps.es);
let phi0 = p.phi0;
let sinphi0 = phi0.sin();
let alpha = (1. + (es * phi0.cos().powi(4)) / (1. - es)).sqrt();
let u0 = (sinphi0 / alpha).asin();
let g = ((1. + e * sinphi0) / (1. - e * sinphi0)).powf(alpha * e / 2.);
let tan_half_phi0_plus_pi_4 = (phi0 / 2. + FRAC_PI_4).tan();
if tan_half_phi0_plus_pi_4 == 0.0 {
return Err(Error::InputStringError(
"Invalid value for lat_0: lat_0 + PI/4 should be different from 0",
));
}
let n0 = (1. - es).sqrt() / (1. - es * sinphi0.powf(2.));
Ok(Projection {
e,
xyfact: (2. * p.x0 / p.ellps.a, 2. * p.y0 / p.ellps.a),
alpha,
k: (u0 / 2. + FRAC_PI_4).tan() / tan_half_phi0_plus_pi_4.powf(alpha) * g,
n: S0.sin(),
rho0: p.k0 * n0 / S0.tan(),
ad: FRAC_PI_2 - UQ,
easting_northing,
})
}
pub fn forward(&self, lam: f64, phi: f64, z: f64) -> Result<(f64, f64, f64)> {
let sinphi = phi.sin();
let gfi = ((1. + self.e * sinphi) / (1. - self.e * sinphi)).powf(self.alpha * self.e / 2.);
let u = 2.
* ((self.k * (phi / 2. + FRAC_PI_4).tan().powf(self.alpha) / gfi).atan() - FRAC_PI_4);
let deltav = -lam * self.alpha;
let s = (self.ad.cos() * u.sin() + self.ad.sin() * u.cos() * deltav.cos()).asin();
let cos_s = s.cos();
Ok(if cos_s < 1.0e-12 {
(0., 0., z)
} else {
let eps = self.n * (u.cos() * deltav.sin() / cos_s).asin();
let rho = self.rho0 * (S0 / 2. + FRAC_PI_4).tan().powf(self.n)
/ (s / 2. + FRAC_PI_4).tan().powf(self.n);
let (x, y) = (rho * eps.sin(), rho * eps.cos());
if self.easting_northing {
(
-x - self.xyfact.0,
-y - self.xyfact.1,
z,
)
} else {
(x, y, z)
}
})
}
fn inverse(&self, x: f64, y: f64, z: f64) -> Result<(f64, f64, f64)> {
let (x, y) = if self.easting_northing {
(-y - self.xyfact.0, -x - self.xyfact.1)
} else {
(y, x)
};
let rho = x.hypot(y);
let eps = y.atan2(x);
let d = eps / S0.sin();
let s = if rho == 0.0 {
FRAC_PI_2
} else {
2. * (((self.rho0 / rho).powf(1. / self.n) * (S0 / 2. + FRAC_PI_4).tan()).atan()
- FRAC_PI_4)
};
let u = (self.ad.cos() * s.sin() - self.ad.sin() * s.cos() * d.cos()).asin();
let deltav = (s.cos() * d.sin() / u.cos()).asin();
let lam = -deltav / self.alpha;
let mut fi1 = u;
let mut phi;
for _ in 0..MAX_ITER {
phi = 2.
* ((self.k.powf(-1. / self.alpha)
* (u / 2. + FRAC_PI_4).tan().powf(1. / self.alpha)
* ((1. + self.e * fi1.sin()) / (1. - self.e * fi1.sin())).powf(self.e / 2.))
.atan()
- FRAC_PI_4);
if (fi1 - phi).abs() < EPS {
return Ok((lam, phi, z));
}
fi1 = phi;
}
Err(Error::CoordTransOutsideProjectionDomain)
}
pub const fn has_inverse() -> bool {
true
}
pub const fn has_forward() -> bool {
true
}
}
#[cfg(test)]
mod tests {
use crate::proj::Proj;
use crate::tests::utils::{test_proj_forward, test_proj_inverse};
#[test]
fn proj_krovak() {
let p = Proj::from_proj_string("+proj=krovak +units=m").unwrap();
println!("{:#?}", p.projection());
let inputs = [
(
(12.09, 47.73, 0.),
(-951555.937880165293, -1276319.151569747366, 0.),
),
(
(22.56, 51.06, 0.),
(-159523.534749580635, -983087.548008236452, 0.),
),
];
test_proj_forward(&p, &inputs, 1e-6);
test_proj_inverse(&p, &inputs, 1e-6);
}
}