use core::fmt;
use super::Angle;
use crate::{
error::{Error, Result},
unit,
};
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Coordinate(f64);
impl Coordinate {
pub fn from_meters(m: f64) -> Result<Self> {
if !m.is_finite() {
return Err(Error::NotFinite);
}
Ok(Coordinate(m))
}
pub fn from_km(km: f64) -> Result<Self> {
Self::from_meters(km * unit::KM)
}
pub fn from_au(au: f64) -> Result<Self> {
Self::from_meters(au * unit::AU)
}
pub fn from_lyr(lyr: f64) -> Result<Self> {
Self::from_meters(lyr * unit::LYR)
}
pub fn from_pc(pc: f64) -> Result<Self> {
Self::from_meters(pc * unit::PC)
}
pub fn from_kpc(kpc: f64) -> Result<Self> {
Self::from_meters(kpc * unit::KPC)
}
pub fn from_mpc(mpc: f64) -> Result<Self> {
Self::from_meters(mpc * unit::MPC)
}
pub fn from_gpc(gpc: f64) -> Result<Self> {
Self::from_meters(gpc * unit::GPC)
}
pub fn from_parallax(parallax: Angle) -> Result<Self> {
let p = parallax.rad();
if p == 0.0 {
return Err(Error::NotFinite);
}
Self::from_meters(unit::AU / p.tan())
}
pub fn m(self) -> f64 {
self.0
}
pub fn km(self) -> f64 {
self.0 / unit::KM
}
pub fn au(self) -> f64 {
self.0 / unit::AU
}
pub fn lyr(self) -> f64 {
self.0 / unit::LYR
}
pub fn pc(self) -> f64 {
self.0 / unit::PC
}
pub fn kpc(self) -> f64 {
self.0 / unit::KPC
}
pub fn mpc(self) -> f64 {
self.0 / unit::MPC
}
pub fn gpc(self) -> f64 {
self.0 / unit::GPC
}
pub fn abs(self) -> Coordinate {
Coordinate(self.0.abs())
}
pub fn parallax(self) -> Result<Angle> {
if self.0 == 0.0 {
return Err(Error::NotFinite);
}
Angle::from_radians((unit::AU / self.0).atan())
}
}
impl fmt::Display for Coordinate {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let decimals = f.precision().unwrap_or(3);
let d = self.0.abs();
let (value, unit_label) = if d < 1e4 {
(self.0, "m")
} else if d < 1e9 {
(self.km(), "km")
} else if d < 1e3 * unit::AU {
(self.au(), "AU")
} else if d < 1e3 * unit::PC {
(self.pc(), "pc")
} else if d < 1e6 * unit::PC {
(self.kpc(), "kpc")
} else if d < 1e9 * unit::PC {
(self.mpc(), "Mpc")
} else {
(self.gpc(), "Gpc")
};
write!(f, "{value:.decimals$} {unit_label}")
}
}
impl approx::AbsDiffEq for Coordinate {
type Epsilon = f64;
fn default_epsilon() -> Self::Epsilon {
1.0
}
fn abs_diff_eq(&self, other: &Self, epsilon: Self::Epsilon) -> bool {
(self.0 - other.0).abs() <= epsilon
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn rejects_non_finite() {
assert!(matches!(
Coordinate::from_meters(f64::NAN),
Err(Error::NotFinite)
));
}
#[test]
fn round_trip_au() {
let d = Coordinate::from_au(1.0).unwrap();
assert!((d.m() - 1.495978707e11).abs() < 1.0);
assert!((d.au() - 1.0).abs() < 1e-12);
}
#[test]
fn parsec_definition() {
let one_pc = Coordinate::from_pc(1.0).unwrap();
let p = one_pc.parallax().unwrap();
assert!((p.arcsec() - 1.0).abs() < 1e-9);
}
#[test]
fn parallax_round_trip() {
let arcsec = Angle::from_arcsec(0.5).unwrap();
let d = Coordinate::from_parallax(arcsec).unwrap();
assert!((d.pc() - 2.0).abs() < 1e-6);
}
}