#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Xy {
pub x: f64,
pub y: f64,
}
impl Xy {
#[must_use]
pub const fn new(x: f64, y: f64) -> Self {
Self { x, y }
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Lp {
pub lam: f64,
pub phi: f64,
}
impl Lp {
#[must_use]
pub const fn new(lam: f64, phi: f64) -> Self {
Self { lam, phi }
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Uv {
pub u: f64,
pub v: f64,
}
impl Uv {
#[must_use]
pub const fn new(u: f64, v: f64) -> Self {
Self { u, v }
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Xyz {
pub x: f64,
pub y: f64,
pub z: f64,
}
impl Xyz {
#[must_use]
pub const fn new(x: f64, y: f64, z: f64) -> Self {
Self { x, y, z }
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Lpz {
pub lam: f64,
pub phi: f64,
pub z: f64,
}
impl Lpz {
#[must_use]
pub const fn new(lam: f64, phi: f64, z: f64) -> Self {
Self { lam, phi, z }
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Uvw {
pub u: f64,
pub v: f64,
pub w: f64,
}
impl Uvw {
#[must_use]
pub const fn new(u: f64, v: f64, w: f64) -> Self {
Self { u, v, w }
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Xyzt {
pub x: f64,
pub y: f64,
pub z: f64,
pub t: f64,
}
impl Xyzt {
#[must_use]
pub const fn new(x: f64, y: f64, z: f64, t: f64) -> Self {
Self { x, y, z, t }
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Lpzt {
pub lam: f64,
pub phi: f64,
pub z: f64,
pub t: f64,
}
impl Lpzt {
#[must_use]
pub const fn new(lam: f64, phi: f64, z: f64, t: f64) -> Self {
Self { lam, phi, z, t }
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Uvwt {
pub u: f64,
pub v: f64,
pub w: f64,
pub t: f64,
}
impl Uvwt {
#[must_use]
pub const fn new(u: f64, v: f64, w: f64, t: f64) -> Self {
Self { u, v, w, t }
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Opk {
pub o: f64,
pub p: f64,
pub k: f64,
}
impl Opk {
#[must_use]
pub const fn new(o: f64, p: f64, k: f64) -> Self {
Self { o, p, k }
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Enu {
pub e: f64,
pub n: f64,
pub u: f64,
}
impl Enu {
#[must_use]
pub const fn new(e: f64, n: f64, u: f64) -> Self {
Self { e, n, u }
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Geod {
pub s: f64,
pub a1: f64,
pub a2: f64,
}
impl Geod {
#[must_use]
pub const fn new(s: f64, a1: f64, a2: f64) -> Self {
Self { s, a1, a2 }
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Coord([f64; 4]);
impl Coord {
#[must_use]
pub const fn new(a: f64, b: f64, c: f64, d: f64) -> Coord {
Coord([a, b, c, d])
}
#[must_use]
pub fn xy(&self) -> Xy {
Xy {
x: self.0[0],
y: self.0[1],
}
}
#[must_use]
pub fn lp(&self) -> Lp {
Lp {
lam: self.0[0],
phi: self.0[1],
}
}
#[must_use]
pub fn uv(&self) -> Uv {
Uv {
u: self.0[0],
v: self.0[1],
}
}
#[must_use]
pub fn xyz(&self) -> Xyz {
Xyz {
x: self.0[0],
y: self.0[1],
z: self.0[2],
}
}
#[must_use]
pub fn lpz(&self) -> Lpz {
Lpz {
lam: self.0[0],
phi: self.0[1],
z: self.0[2],
}
}
#[must_use]
pub fn uvw(&self) -> Uvw {
Uvw {
u: self.0[0],
v: self.0[1],
w: self.0[2],
}
}
#[must_use]
pub fn xyzt(&self) -> Xyzt {
Xyzt {
x: self.0[0],
y: self.0[1],
z: self.0[2],
t: self.0[3],
}
}
#[must_use]
pub fn lpzt(&self) -> Lpzt {
Lpzt {
lam: self.0[0],
phi: self.0[1],
z: self.0[2],
t: self.0[3],
}
}
#[must_use]
pub fn uvwt(&self) -> Uvwt {
Uvwt {
u: self.0[0],
v: self.0[1],
w: self.0[2],
t: self.0[3],
}
}
#[must_use]
pub fn opk(&self) -> Opk {
Opk {
o: self.0[0],
p: self.0[1],
k: self.0[2],
}
}
#[must_use]
pub fn enu(&self) -> Enu {
Enu {
e: self.0[0],
n: self.0[1],
u: self.0[2],
}
}
#[must_use]
pub fn geod(&self) -> Geod {
Geod {
s: self.0[0],
a1: self.0[1],
a2: self.0[2],
}
}
#[must_use]
pub fn v(&self) -> [f64; 4] {
self.0
}
#[must_use]
pub fn get(&self, i: usize) -> Option<f64> {
self.0.get(i).copied()
}
#[must_use]
pub fn error() -> Coord {
Coord([f64::INFINITY; 4])
}
#[must_use]
pub fn is_error(&self) -> bool {
self.0.iter().any(|c| !c.is_finite())
}
}
impl core::ops::Index<usize> for Coord {
type Output = f64;
fn index(&self, i: usize) -> &f64 {
&self.0[i]
}
}
impl From<Xy> for Coord {
fn from(value: Xy) -> Self {
Coord([value.x, value.y, 0.0, 0.0])
}
}
impl From<Lp> for Coord {
fn from(value: Lp) -> Self {
Coord([value.lam, value.phi, 0.0, 0.0])
}
}
impl From<Uv> for Coord {
fn from(value: Uv) -> Self {
Coord([value.u, value.v, 0.0, 0.0])
}
}
impl From<Xyz> for Coord {
fn from(value: Xyz) -> Self {
Coord([value.x, value.y, value.z, 0.0])
}
}
impl From<Lpz> for Coord {
fn from(value: Lpz) -> Self {
Coord([value.lam, value.phi, value.z, 0.0])
}
}
impl From<Uvw> for Coord {
fn from(value: Uvw) -> Self {
Coord([value.u, value.v, value.w, 0.0])
}
}
impl From<Xyzt> for Coord {
fn from(value: Xyzt) -> Self {
Coord([value.x, value.y, value.z, value.t])
}
}
impl From<Lpzt> for Coord {
fn from(value: Lpzt) -> Self {
Coord([value.lam, value.phi, value.z, value.t])
}
}
impl From<Uvwt> for Coord {
fn from(value: Uvwt) -> Self {
Coord([value.u, value.v, value.w, value.t])
}
}
impl From<Opk> for Coord {
fn from(value: Opk) -> Self {
Coord([value.o, value.p, value.k, 0.0])
}
}
impl From<Enu> for Coord {
fn from(value: Enu) -> Self {
Coord([value.e, value.n, value.u, 0.0])
}
}
impl From<Geod> for Coord {
fn from(value: Geod) -> Self {
Coord([value.s, value.a1, value.a2, 0.0])
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[repr(i32)]
pub enum Direction {
Fwd = 1,
Ident = 0,
Inv = -1,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn lp_round_trip() {
let lp = Lp::new(0.5, 0.3);
let c: Coord = lp.into();
assert_eq!(c.lp(), lp);
}
#[test]
fn xy_round_trip() {
let xy = Xy::new(123.0, 456.0);
let c: Coord = xy.into();
assert_eq!(c.xy(), xy);
}
#[test]
fn xyz_round_trip() {
let xyz = Xyz::new(1.0, 2.0, 3.0);
let c: Coord = xyz.into();
assert_eq!(c.xyz(), xyz);
}
#[test]
fn xyzt_round_trip() {
let xyzt = Xyzt::new(1.0, 2.0, 3.0, 4.0);
let c: Coord = xyzt.into();
assert_eq!(c.xyzt(), xyzt);
}
#[test]
fn lpzt_round_trip() {
let lpzt = Lpzt::new(0.5, 0.3, 100.0, 2020.0);
let c: Coord = lpzt.into();
assert_eq!(c.lpzt(), lpzt);
}
#[test]
fn error_sentinel_is_error() {
assert!(Coord::error().is_error());
}
#[test]
fn finite_coord_is_not_error() {
assert!(!Coord::new(1.0, 2.0, 3.0, 4.0).is_error());
}
#[test]
fn nan_component_is_error() {
assert!(Coord::new(f64::NAN, 0.0, 0.0, 0.0).is_error());
}
#[test]
fn direction_discriminants() {
assert_eq!(Direction::Fwd as i32, 1);
assert_eq!(Direction::Ident as i32, 0);
assert_eq!(Direction::Inv as i32, -1);
}
#[test]
fn v_returns_all_slots() {
assert_eq!(Coord::new(1.0, 2.0, 3.0, 4.0).v(), [1.0, 2.0, 3.0, 4.0]);
}
#[test]
fn get_bounds_checked() {
assert_eq!(Coord::new(1.0, 2.0, 3.0, 4.0).get(0), Some(1.0));
assert_eq!(Coord::new(1.0, 2.0, 3.0, 4.0).get(9), None);
}
#[test]
fn index_reads_slots() {
let c = Coord::new(10.0, 20.0, 30.0, 40.0);
assert_eq!(c[0], 10.0);
assert_eq!(c[3], 40.0);
}
#[test]
fn other_views_share_slots() {
let c = Coord::new(7.0, 8.0, 9.0, 0.0);
assert_eq!(c.uv(), Uv::new(7.0, 8.0));
assert_eq!(c.lpz(), Lpz::new(7.0, 8.0, 9.0));
assert_eq!(c.uvw(), Uvw::new(7.0, 8.0, 9.0));
assert_eq!(c.opk(), Opk::new(7.0, 8.0, 9.0));
assert_eq!(c.enu(), Enu::new(7.0, 8.0, 9.0));
assert_eq!(c.geod(), Geod::new(7.0, 8.0, 9.0));
}
#[test]
fn remaining_from_conversions() {
assert_eq!(Coord::from(Uv::new(1.0, 2.0)).v(), [1.0, 2.0, 0.0, 0.0]);
assert_eq!(
Coord::from(Lpz::new(1.0, 2.0, 3.0)).v(),
[1.0, 2.0, 3.0, 0.0]
);
assert_eq!(
Coord::from(Uvw::new(1.0, 2.0, 3.0)).v(),
[1.0, 2.0, 3.0, 0.0]
);
assert_eq!(
Coord::from(Uvwt::new(1.0, 2.0, 3.0, 4.0)).v(),
[1.0, 2.0, 3.0, 4.0]
);
assert_eq!(
Coord::from(Opk::new(1.0, 2.0, 3.0)).v(),
[1.0, 2.0, 3.0, 0.0]
);
assert_eq!(
Coord::from(Enu::new(1.0, 2.0, 3.0)).v(),
[1.0, 2.0, 3.0, 0.0]
);
assert_eq!(
Coord::from(Geod::new(1.0, 2.0, 3.0)).v(),
[1.0, 2.0, 3.0, 0.0]
);
}
}