Skip to main content

pleiades_types/
angles.rs

1//! Angular quantity primitives: [`Angle`], [`Longitude`], and [`Latitude`].
2
3use core::fmt;
4
5/// An angular quantity measured in degrees.
6///
7/// `Angle` is intentionally neutral: it does not assume a normalization range.
8/// Use [`Angle::normalized_0_360`] or [`Angle::normalized_signed`] when a
9/// canonical wrap is required.
10#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
11#[derive(Clone, Copy, Debug, Default, PartialEq)]
12pub struct Angle(f64);
13
14impl Angle {
15    /// Creates a new angle measured in degrees.
16    pub const fn from_degrees(degrees: f64) -> Self {
17        Self(degrees)
18    }
19
20    /// Creates a new angle measured in radians.
21    pub fn from_radians(radians: f64) -> Self {
22        Self(radians.to_degrees())
23    }
24
25    /// Returns the underlying angle in degrees.
26    pub const fn degrees(self) -> f64 {
27        self.0
28    }
29
30    /// Returns the angle in radians.
31    pub fn radians(self) -> f64 {
32        self.0.to_radians()
33    }
34
35    /// Returns the angle normalized into the half-open range `[0, 360)`.
36    pub fn normalized_0_360(self) -> Self {
37        Self(self.0.rem_euclid(360.0))
38    }
39
40    /// Returns the angle normalized into the signed range `[-180, 180)`.
41    pub fn normalized_signed(self) -> Self {
42        let degrees = self.normalized_0_360().degrees();
43        if degrees >= 180.0 {
44            Self(degrees - 360.0)
45        } else {
46            Self(degrees)
47        }
48    }
49
50    /// Returns `true` when the underlying numeric value is finite.
51    pub const fn is_finite(self) -> bool {
52        self.0.is_finite()
53    }
54}
55
56impl fmt::Display for Angle {
57    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
58        write!(f, "{}°", self.0)
59    }
60}
61
62/// A canonical ecliptic or longitude-like angle normalized into `[0, 360)`.
63#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
64#[derive(Clone, Copy, Debug, Default, PartialEq)]
65pub struct Longitude(Angle);
66
67impl Longitude {
68    /// Creates a longitude normalized into `[0, 360)`.
69    pub fn from_degrees(degrees: f64) -> Self {
70        Self(Angle::from_degrees(degrees).normalized_0_360())
71    }
72
73    /// Returns the longitude in degrees, already normalized into `[0, 360)`.
74    pub const fn degrees(self) -> f64 {
75        self.0.degrees()
76    }
77
78    /// Returns the underlying angle wrapper.
79    pub const fn angle(self) -> Angle {
80        self.0
81    }
82}
83
84impl From<Angle> for Longitude {
85    fn from(value: Angle) -> Self {
86        Self::from_degrees(value.degrees())
87    }
88}
89
90impl fmt::Display for Longitude {
91    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
92        fmt::Display::fmt(&self.0, f)
93    }
94}
95
96/// A signed latitude-like angle measured in degrees, north-positive.
97///
98/// Positive values lie north of the equator (or the relevant reference plane)
99/// and negative values lie south. Latitude values are not automatically
100/// clamped; the caller is expected to provide values consistent with the
101/// relevant coordinate system.
102#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
103#[derive(Clone, Copy, Debug, Default, PartialEq)]
104pub struct Latitude(Angle);
105
106impl Latitude {
107    /// Creates a latitude measured in degrees.
108    pub const fn from_degrees(degrees: f64) -> Self {
109        Self(Angle::from_degrees(degrees))
110    }
111
112    /// Returns the latitude in degrees.
113    pub const fn degrees(self) -> f64 {
114        self.0.degrees()
115    }
116
117    /// Returns the underlying angle wrapper.
118    pub const fn angle(self) -> Angle {
119        self.0
120    }
121}
122
123impl From<Angle> for Latitude {
124    fn from(value: Angle) -> Self {
125        Self::from_degrees(value.degrees())
126    }
127}
128
129impl fmt::Display for Latitude {
130    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
131        fmt::Display::fmt(&self.0, f)
132    }
133}