use crate::errors::{Error, Result};
use crate::math::consts::{FRAC_PI_2, PI, TAU};
const GENAU: f64 = 1.0e-12;
const GENAU2: f64 = GENAU * GENAU;
const MAXITER: usize = 30;
const FRAC_PI_2_EPS: f64 = 1.001 * FRAC_PI_2;
pub fn geodetic_to_geocentric(x: f64, y: f64, z: f64, a: f64, es: f64) -> Result<(f64, f64, f64)> {
let mut lon = x;
let mut lat = y;
if lat < -FRAC_PI_2 && lat > -FRAC_PI_2_EPS {
lat = -FRAC_PI_2
} else if lat > FRAC_PI_2 && lat < FRAC_PI_2_EPS {
lat = FRAC_PI_2
} else if !(-FRAC_PI_2..=FRAC_PI_2).contains(&lat) {
return Err(Error::LatitudeOutOfRange);
};
if lon > PI {
lon -= TAU;
}
let (sin_lat, cos_lat) = lat.sin_cos();
let rn = a / (1. - es * (sin_lat * sin_lat)).sqrt();
Ok((
(rn + z) * cos_lat * lon.cos(),
(rn + z) * cos_lat * lon.sin(),
((rn * (1. - es)) + z) * sin_lat,
))
}
pub fn geocentric_to_geodetic(
x: f64,
y: f64,
z: f64,
a: f64,
es: f64,
b: f64,
) -> Result<(f64, f64, f64)> {
let d2 = (x * x) + (y * y);
let p = d2.sqrt();
let rr = (d2 + z * z).sqrt();
let lon = if p / a < GENAU {
if rr / a < GENAU {
return Ok((0., FRAC_PI_2, -b));
}
0.
} else {
y.atan2(x)
};
let ct = z / rr;
let st = p / rr;
let mut rx = 1.0 / (1.0 - es * (2.0 - es) * st * st).sqrt();
let mut cphi0 = st * (1.0 - es) * rx;
let mut sphi0 = ct * rx;
let (mut rk, mut rn, mut cphi, mut sphi, mut sdphi, mut height);
let mut iter = 0;
loop {
iter += 1;
rn = a / (1.0 - es * sphi0 * sphi0).sqrt();
height = p * cphi0 + z * sphi0 - rn * (1.0 - es * sphi0 * sphi0);
if (rn + height) == 0. {
return Ok((lon, 0., height));
}
rk = es * rn / (rn + height);
rx = 1.0 / (1.0 - rk * (2.0 - rk) * st * st).sqrt();
cphi = st * (1.0 - rk) * rx;
sphi = ct * rx;
sdphi = sphi * cphi0 - cphi * sphi0;
cphi0 = cphi;
sphi0 = sphi;
if sdphi * sdphi <= GENAU2 {
break;
}
if iter >= MAXITER {
break;
}
}
Ok((lon, sphi.atan2(cphi.abs()), height))
}