Skip to main content

irox_units/units/
angle.rs

1// SPDX-License-Identifier: MIT
2// Copyright 2025 IROX Contributors
3//
4
5//!
6//! This module contains the basic types and conversions for the SI coherent derived "Planar Angle"
7//! quantity
8use core::fmt::{Display, Formatter};
9
10use crate::units::{FromUnits, Unit};
11
12///
13/// Represents a specific Planar Angle unit - SI or otherwise
14#[derive(Debug, Clone, Copy, Default, Eq, PartialEq)]
15#[non_exhaustive]
16pub enum AngleUnits {
17    /// SI Base Unit for Planar Angle - Radians, the unit radius of a circle.
18    /// There are `tau` Radians across a full circle
19    #[default]
20    Radians,
21
22    /// Derived unit for Planar Angle - Degrees.  
23    /// There are 360 degrees across a full circle
24    Degrees,
25
26    /// Derived unit for Planar Angle - Minutes - the first division of a degree
27    /// There are 60 minutes in a degree.
28    Minutes,
29
30    /// Derived unit for Planar Angle - Seconds - the second division of a degree
31    /// There are 60 seconds in a minute.
32    Seconds,
33
34    /// Derived unit for Planar Angle - Revolution (turn) - a full circuit of a circle
35    /// There are 360 degrees in a revolution
36    Revolutions,
37
38    /// Derived unit for Planar Angle - NATO Mil
39    /// There are 6400 mils in a turn/revolution
40    Mils,
41}
42
43macro_rules! from_units_angle {
44    ($type:ident) => {
45        impl crate::units::FromUnits<$type> for AngleUnits {
46            fn from(&self, value: $type, units: Self) -> $type {
47                match self {
48                    AngleUnits::Degrees => match units {
49                        AngleUnits::Radians => value * RAD_2_DEG as $type,
50                        AngleUnits::Degrees => value as $type,
51
52                        AngleUnits::Minutes => value * RAD_2_DEG as $type * DEG_2_MIN as $type,
53                        AngleUnits::Seconds => value * RAD_2_DEG as $type * DEG_2_SEC as $type,
54                        AngleUnits::Revolutions => value / REV_2_RAD as $type,
55                        AngleUnits::Mils => value * RAD_2_MIL as $type,
56                    },
57                    AngleUnits::Radians => match units {
58                        AngleUnits::Degrees => value * DEG_2_RAD as $type,
59                        AngleUnits::Radians => value as $type,
60
61                        AngleUnits::Minutes => value * DEG_2_MIN as $type,
62                        AngleUnits::Seconds => value * DEG_2_SEC as $type,
63                        AngleUnits::Revolutions => value / REV_2_DEG as $type,
64                        AngleUnits::Mils => value * DEG_2_MIL as $type,
65                    },
66                    _ => todo!(),
67                }
68            }
69        }
70    };
71}
72
73basic_unit!(Angle, AngleUnits, Degrees);
74from_units_angle!(f32);
75from_units_angle!(f64);
76
77impl Angle {
78    #[must_use]
79    pub const fn new_radians(value: f64) -> Angle {
80        Self::new(value, AngleUnits::Radians)
81    }
82
83    #[must_use]
84    pub const fn new_degrees(value: f64) -> Angle {
85        Self::new(value, AngleUnits::Degrees)
86    }
87
88    #[must_use]
89    pub fn new_dms(degrees: i16, minutes: u8, seconds: f64) -> Angle {
90        let mult: f64 = match degrees {
91            ..=0 => -1.0,
92            _ => 1.0,
93        };
94        let minutes: f64 = f64::from(minutes) * mult;
95        let seconds: f64 = seconds * mult;
96        let value = f64::from(degrees) + minutes / 60. + seconds / 3600.;
97        Self::new_degrees(value)
98    }
99
100    #[must_use]
101    #[allow(unused_imports)]
102    pub fn new_dm(degrees: i16, minutes: f64) -> Angle {
103        use irox_tools::f64::FloatExt;
104        let seconds = minutes.fract() * 60.;
105        Self::new_dms(degrees, minutes.trunc() as u8, seconds)
106    }
107
108    #[must_use]
109    pub fn as_degrees(&self) -> Angle {
110        self.as_unit(AngleUnits::Degrees)
111    }
112
113    #[must_use]
114    pub fn as_radians(&self) -> Angle {
115        self.as_unit(AngleUnits::Radians)
116    }
117
118    #[must_use]
119    pub fn as_dms(&self) -> (i16, u8, f64) {
120        let (deg, val) = self.as_deg_min();
121
122        let min = val as u8;
123        let sec = (val - min as f64) * 60.;
124        (deg, min, sec)
125    }
126
127    #[must_use]
128    #[allow(unused_imports)]
129    pub fn as_deg_min(&self) -> (i16, f64) {
130        use irox_tools::f64::FloatExt;
131        let val = self.as_degrees().value;
132        let sign = val.signum() as i16;
133        let val = val.abs();
134
135        let deg = val as i16;
136        let min = (val - deg as f64) * 60.;
137        (deg * sign, min)
138    }
139
140    pub fn sin(&self) -> f64 {
141        use irox_tools::f64::FloatExt;
142        let v = self.as_radians().value;
143        FloatExt::sin(v)
144    }
145    #[cfg(feature = "std")]
146    pub fn asin(&self) -> f64 {
147        self.as_radians().value.asin()
148    }
149    pub fn cos(&self) -> f64 {
150        use irox_tools::f64::FloatExt;
151        let v = self.as_radians().value;
152        FloatExt::cos(v)
153    }
154    #[cfg(feature = "std")]
155    pub fn acos(&self) -> f64 {
156        self.as_radians().value.acos()
157    }
158
159    #[cfg(feature = "std")]
160    pub fn tan(&self) -> f64 {
161        self.as_radians().value.tan()
162    }
163
164    #[must_use]
165    pub const fn min_value() -> Self {
166        Angle::new_degrees(0.0)
167    }
168}
169
170impl Display for Angle {
171    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
172        write!(f, "{:03.3}\u{00B0}", self.as_degrees().value)
173    }
174}
175
176/// Degree to Radians factor
177pub const DEG_2_RAD: f64 = 0.017_453_292_519_943_295;
178/// Radians to Degrees factor
179pub const RAD_2_DEG: f64 = 57.295_779_513_082_32;
180/// Revolutions to Degrees factor
181pub const REV_2_DEG: f64 = 360.;
182/// Revolutions to Radians factor
183pub const REV_2_RAD: f64 = core::f64::consts::TAU;
184/// Minutes to Seconds factor
185pub const MIN_2_SEC: f64 = 60.;
186/// Mils to Revolutions factor
187pub const MIL_2_REV: f64 = 6400.;
188/// Degrees to Minutes factor
189pub const DEG_2_MIN: f64 = 60.;
190/// Degrees to Seconds factor
191pub const DEG_2_SEC: f64 = DEG_2_MIN * MIN_2_SEC;
192/// Degrees to Mils factor
193pub const DEG_2_MIL: f64 = MIL_2_REV / REV_2_DEG;
194pub const RAD_2_MIL: f64 = MIL_2_REV / REV_2_RAD;