use crate::errors::{Error, Result};
use crate::parameters::ParamList;
use crate::proj::ProjData;
super::projection! { geos }
#[derive(Debug, Clone)]
pub(crate) enum Projection {
El(Ell),
Sp(Sph),
}
impl Projection {
pub fn geos(p: &mut ProjData, params: &ParamList) -> Result<Self> {
let h: f64 = params
.try_value("h")?
.ok_or(Error::InputStringError("Missing parameter 'h'"))?;
let flip_axis: bool = params
.try_value::<&str>("sweep")
.and_then(|sweep| match sweep {
None => Ok(false),
Some("y") => Ok(false),
Some("x") => Ok(true),
Some(_other) => Err(Error::InvalidParameterValue(
"sweep require only 'x' or 'y' value",
)),
})?;
let radius_g_1 = h / p.ellps.a;
if radius_g_1 <= 0. || radius_g_1 >= 1.0e10 {
return Err(Error::InvalidParameterValue("Invalid value for 'h'."));
}
let radius_g = 1. + radius_g_1;
let c = radius_g * radius_g - 1.0;
if p.ellps.is_ellipsoid() {
Ok(Self::El(Ell {
radius_p: p.ellps.one_es.sqrt(),
radius_p2: p.ellps.one_es,
radius_p_inv2: p.ellps.rone_es,
radius_g,
radius_g_1,
c,
flip_axis,
}))
} else {
Ok(Self::Sp(Sph {
radius_g,
radius_g_1,
c,
flip_axis,
}))
}
}
#[inline(always)]
pub fn forward(&self, lam: f64, phi: f64, z: f64) -> Result<(f64, f64, f64)> {
match self {
Self::El(p) => p.forward(lam, phi, z),
Self::Sp(p) => p.forward(lam, phi, z),
}
}
#[inline(always)]
pub fn inverse(&self, x: f64, y: f64, z: f64) -> Result<(f64, f64, f64)> {
match self {
Self::El(p) => p.inverse(x, y, z),
Self::Sp(p) => p.inverse(x, y, z),
}
}
pub const fn has_inverse() -> bool {
true
}
pub const fn has_forward() -> bool {
true
}
}
#[derive(Debug, Clone)]
pub(crate) struct Ell {
radius_p: f64,
radius_p2: f64,
radius_p_inv2: f64,
radius_g: f64,
radius_g_1: f64,
c: f64,
flip_axis: bool,
}
impl Ell {
pub fn forward(&self, lam: f64, phi: f64, z: f64) -> Result<(f64, f64, f64)> {
let (sin_phi, cos_phi) = (self.radius_p2 * phi.tan()).atan().sin_cos();
let r = self.radius_p / (self.radius_p * cos_phi).hypot(sin_phi);
let vx = r * lam.cos() * cos_phi;
let vy = r * lam.sin() * cos_phi;
let vz = r * sin_phi;
if ((self.radius_g - vx) * vx - vy * vy - vz * vz * self.radius_p_inv2) < 0. {
return Err(Error::CoordTransOutsideProjectionDomain);
}
let tmp = self.radius_g - vx;
if self.flip_axis {
Ok((
self.radius_g_1 * (vy / vz.hypot(tmp)).atan(),
self.radius_g_1 * (vz / tmp).atan(),
z,
))
} else {
Ok((
self.radius_g_1 * (vy / tmp).atan(),
self.radius_g_1 * (vz / vy.hypot(tmp)).atan(),
z,
))
}
}
fn inverse(&self, x: f64, y: f64, z: f64) -> Result<(f64, f64, f64)> {
let mut vx = -1.0;
let mut vy;
let mut vz;
if self.flip_axis {
vz = (y / self.radius_g_1).tan();
vy = (x / self.radius_g_1).tan() * 1.0_f64.hypot(vz);
} else {
vy = (x / self.radius_g_1).tan();
vz = (y / self.radius_g_1).tan() * 1.0_f64.hypot(vy);
}
let mut a = vz / self.radius_p;
a = vy * vy + a * a + vx * vx;
let b = 2.0 * self.radius_g * vx;
let det = b * b - 4.0 * a * self.c;
if det < 0. {
return Err(Error::CoordTransOutsideProjectionDomain);
}
let k = (-b - det.sqrt()) / (2. * a);
vx = self.radius_g + k * vx;
vy *= k;
vz *= k;
let lam = vy.atan2(vx);
Ok((
lam,
(self.radius_p_inv2 * ((vz * lam.cos() / vx).atan())),
z,
))
}
}
#[derive(Debug, Clone)]
pub(crate) struct Sph {
radius_g: f64,
radius_g_1: f64,
c: f64,
flip_axis: bool,
}
impl Sph {
pub fn forward(&self, lam: f64, phi: f64, z: f64) -> Result<(f64, f64, f64)> {
let mut tmp = phi.cos();
let vx = tmp * lam.cos();
let vy = tmp * lam.sin();
let vz = phi.sin();
tmp = self.radius_g - vx;
if self.flip_axis {
Ok((
self.radius_g_1 * (vy / vz.hypot(tmp)).atan(),
self.radius_g_1 * (vz / tmp).atan(),
z,
))
} else {
Ok((
self.radius_g_1 * (vy / tmp).atan(),
self.radius_g_1 * (vz / vy.hypot(tmp)).atan(),
z,
))
}
}
fn inverse(&self, x: f64, y: f64, z: f64) -> Result<(f64, f64, f64)> {
let mut vx = -1.0;
let mut vy;
let mut vz;
if self.flip_axis {
vz = (y / self.radius_g_1).tan();
vy = (x / self.radius_g_1).tan() * (1.0 + vz * vz).sqrt();
} else {
vy = (x / self.radius_g_1).tan();
vz = (y / self.radius_g_1).tan() * (1.0 + vy * vy).sqrt();
}
let a = vy * vy + vz * vz + vx * vx;
let b = 2.0 * self.radius_g * vx;
let det = b * b - 4.0 * a * self.c;
if det < 0. {
return Err(Error::CoordTransOutsideProjectionDomain);
}
let k = (-b - det.sqrt()) / (2. * a);
vx = self.radius_g + k * vx;
vy *= k;
vz *= k;
let lam = vy.atan2(vx);
Ok((lam, (vz * lam.cos() / vx).atan(), z))
}
}
#[cfg(test)]
mod tests {
use crate::math::consts::{EPS_10, EPS_7};
use crate::proj::Proj;
use crate::tests::utils::{test_proj_forward, test_proj_inverse};
#[test]
fn proj_geos_el() {
let p = Proj::from_proj_string("+proj=geos +lon_0=0 +h=35785782.858 +x_0=0 +y_0=0 +a=6378160 +b=6356775 +units=m +no_defs")
.unwrap();
println!("{:#?}", p.projection());
let inputs = [
(
(18.763481601401576, 9.204293875870595, 0.),
(2000000.0, 1000000.0, 0.),
),
(
(18.763481601401576, -9.204293875870595, 0.),
(2000000.0, -1000000.0, 0.),
),
(
(-18.763481601401576, 9.204293875870595, 0.),
(-2000000.0, 1000000.0, 0.),
),
(
(-18.763481601401576, -9.204293875870595, 0.),
(-2000000.0, -1000000.0, 0.),
),
];
test_proj_forward(&p, &inputs, 1e-8);
test_proj_inverse(&p, &inputs, 1.0e-2);
}
#[test]
fn proj_geos_sp() {
let p = Proj::from_proj_string("+proj=geos +lon_0=0 +h=35785833.8833").unwrap();
println!("{:#?}", p.projection());
let inputs = [
(
(18.763554109081273, 9.204326881322723, 0.),
(2000000.0, 1000000.0, 0.),
),
(
(18.763554109081273, -9.204326881322723, 0.),
(2000000.0, -1000000.0, 0.),
),
(
(-18.763554109081273, 9.204326881322723, 0.),
(-2000000.0, 1000000.0, 0.),
),
(
(-18.763554109081273, -9.204326881322723, 0.),
(-2000000.0, -1000000.0, 0.),
),
];
test_proj_forward(&p, &inputs, 1.0e-8);
test_proj_inverse(&p, &inputs, 1.0e-2);
}
}