use crate::coord::{Coord, Lp, Lpz, Xy, Xyz};
use crate::error::{ProjError, ProjResult};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[repr(i32)]
pub enum IoUnits {
Whatever = 0,
Classic = 1,
Projected = 2,
Cartesian = 3,
Radians = 4,
Degrees = 5,
}
pub trait Operation: core::fmt::Debug + Send + Sync {
fn forward_2d(&self, lp: Lp) -> ProjResult<Xy> {
let _ = lp;
Err(ProjError::InvalidOp)
}
fn inverse_2d(&self, xy: Xy) -> ProjResult<Lp> {
let _ = xy;
Err(ProjError::NoInverseOp)
}
fn forward_3d(&self, lpz: Lpz) -> ProjResult<Xyz> {
let xy = self.forward_2d(Lp::new(lpz.lam, lpz.phi))?;
Ok(Xyz::new(xy.x, xy.y, lpz.z))
}
fn inverse_3d(&self, xyz: Xyz) -> ProjResult<Lpz> {
let lp = self.inverse_2d(Xy::new(xyz.x, xyz.y))?;
Ok(Lpz::new(lp.lam, lp.phi, xyz.z))
}
fn forward_4d(&self, c: Coord) -> ProjResult<Coord> {
let lpz = c.lpz();
let t = c.v()[3];
let xyz = self.forward_3d(lpz)?;
Ok(Coord::new(xyz.x, xyz.y, xyz.z, t))
}
fn inverse_4d(&self, c: Coord) -> ProjResult<Coord> {
let xyz = c.xyz();
let t = c.v()[3];
let lpz = self.inverse_3d(xyz)?;
Ok(Coord::new(lpz.lam, lpz.phi, lpz.z, t))
}
fn has_inverse(&self) -> bool {
true
}
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(feature = "no_std")]
use alloc::boxed::Box;
fn approx(a: f64, b: f64) -> bool {
(a - b).abs() < 1e-12
}
#[derive(Debug)]
struct Shift;
impl Operation for Shift {
fn forward_2d(&self, lp: Lp) -> ProjResult<Xy> {
Ok(Xy::new(lp.lam + 1.0, lp.phi + 2.0))
}
fn inverse_2d(&self, xy: Xy) -> ProjResult<Lp> {
Ok(Lp::new(xy.x - 1.0, xy.y - 2.0))
}
}
#[test]
fn shift_forward_3d_passes_z_through() -> ProjResult<()> {
let op = Shift;
let result = op.forward_3d(Lpz::new(0.1, 0.2, 99.0))?;
assert!(approx(result.z, 99.0));
assert!(approx(result.x, 1.1));
assert!(approx(result.y, 2.2));
Ok(())
}
#[test]
fn shift_forward_4d_passes_z_and_t_through() -> ProjResult<()> {
let op = Shift;
let result = op.forward_4d(Coord::new(0.1, 0.2, 99.0, 2020.0))?;
assert!(approx(result.v()[2], 99.0));
assert!(approx(result.v()[3], 2020.0));
assert!(approx(result.v()[0], 1.1));
assert!(approx(result.v()[1], 2.2));
Ok(())
}
#[test]
fn shift_inverse_4d_round_trips() -> ProjResult<()> {
let op = Shift;
let c = Coord::new(0.1, 0.2, 99.0, 2020.0);
let f = op.forward_4d(c)?;
let r = op.inverse_4d(f)?;
assert!(approx(r.v()[0], 0.1));
assert!(approx(r.v()[1], 0.2));
assert!(approx(r.v()[2], 99.0));
assert!(approx(r.v()[3], 2020.0));
Ok(())
}
#[derive(Debug)]
struct Identity4d;
impl Operation for Identity4d {
fn forward_4d(&self, c: Coord) -> ProjResult<Coord> {
Ok(c)
}
fn inverse_4d(&self, c: Coord) -> ProjResult<Coord> {
Ok(c)
}
fn has_inverse(&self) -> bool {
true
}
}
#[test]
fn identity4d_forward_works_directly() -> ProjResult<()> {
let op = Identity4d;
let result = op.forward_4d(Coord::new(5.0, 6.0, 7.0, 8.0))?;
assert!(approx(result.v()[0], 5.0));
assert!(approx(result.v()[1], 6.0));
assert!(approx(result.v()[2], 7.0));
assert!(approx(result.v()[3], 8.0));
Ok(())
}
#[test]
fn identity4d_forward_2d_returns_default_invalid_op() {
let op = Identity4d;
assert!(op.forward_2d(Lp::new(0.0, 0.0)) == Err(ProjError::InvalidOp));
}
#[test]
fn identity4d_has_inverse_true() {
let op = Identity4d;
assert!(op.has_inverse());
}
#[derive(Debug)]
struct NoInverse;
impl Operation for NoInverse {
fn forward_2d(&self, lp: Lp) -> ProjResult<Xy> {
Ok(Xy::new(lp.lam, lp.phi))
}
fn has_inverse(&self) -> bool {
false
}
}
#[test]
fn no_inverse_reports_false() {
assert!(!NoInverse.has_inverse());
assert!(NoInverse.inverse_2d(Xy::new(0.0, 0.0)) == Err(ProjError::NoInverseOp));
}
#[test]
fn io_units_discriminants() {
assert!(IoUnits::Whatever as i32 == 0);
assert!(IoUnits::Classic as i32 == 1);
assert!(IoUnits::Projected as i32 == 2);
assert!(IoUnits::Cartesian as i32 == 3);
assert!(IoUnits::Radians as i32 == 4);
assert!(IoUnits::Degrees as i32 == 5);
}
#[test]
fn operation_is_object_safe() -> ProjResult<()> {
let b: Box<dyn Operation> = Box::new(Shift);
let xy = b.forward_2d(Lp::new(0.0, 0.0))?;
assert!(approx(xy.x, 1.0));
assert!(approx(xy.y, 2.0));
Ok(())
}
}