starstuff_types/
angle.rs

1/*!
2Basic angle structures (Radian, Degree, Arc Hour)
3 */
4
5use auto_ops::*;
6
7pub use std::f64::consts::PI;
8
9/// 2π
10pub const TWO_PI: f64 = 2.0 * PI;
11/// π/2
12pub const PI_HALF: f64 = PI / 2.0;
13/// π/4
14pub const PI_FOURTH: f64 = PI / 4.0;
15
16/// Enum containing common angle variants
17#[derive(Copy, Clone, Debug, PartialEq)]
18pub enum Angle {
19    /// Variant containing decimal degree
20    Degree(f64),
21    /// Variant containing decimal radian
22    Radian(f64),
23    /// Variant containing decimal hour
24    Hour(f64),
25}
26
27impl Angle {
28    /// Convert to decimal degree
29    pub fn to_deg(&self) -> f64 {
30        match self {
31            Self::Degree(deg) => *deg,
32            Self::Hour(hr) => 15.0 * hr,
33            Self::Radian(rad) => {
34                const RAD_TO_DEG: f64 = 180.0 / PI;
35                RAD_TO_DEG * rad
36            }
37        }
38    }
39    /// Convert to decimal radian
40    pub fn to_rad(&self) -> f64 {
41        match self {
42            Self::Degree(deg) => {
43                const DEG_TO_RAD: f64 = PI / 180.0;
44                DEG_TO_RAD * deg
45            }
46            Self::Hour(hr) => {
47                const HOUR_TO_RAD: f64 = 15.0 * PI / 180.0;
48                HOUR_TO_RAD * hr
49            }
50            Self::Radian(rad) => *rad,
51        }
52    }
53    /// Convert to decimal hour
54    pub fn to_hr(&self) -> f64 {
55        match self {
56            Self::Degree(deg) => deg / 15.0,
57            Self::Hour(hr) => *hr,
58            Self::Radian(rad) => {
59                const RAD_TO_HOUR: f64 = 180.0 / PI / 15.0;
60                RAD_TO_HOUR * rad
61            }
62        }
63    }
64    pub fn sin(&self) -> f64 {
65        Self::to_rad(self).sin()
66    }
67    pub fn cos(&self) -> f64 {
68        Self::to_rad(self).cos()
69    }
70    pub fn tan(&self) -> f64 {
71        Self::to_rad(self).tan()
72    }
73}
74
75impl_op_ex!(+ |a: &Angle, b: &Angle| -> Angle { match a {
76    Angle::Degree(deg) => Angle::Degree(deg + b.to_deg()),
77    Angle::Radian(rad) => Angle::Degree(rad + b.to_rad()),
78    Angle::Hour(hr) => Angle::Degree(hr + b.to_hr()),
79} });
80
81impl_op_ex!(-|a: &Angle, b: &Angle| -> Angle {
82    match a {
83        Angle::Degree(deg) => Angle::Degree(deg - b.to_deg()),
84        Angle::Radian(rad) => Angle::Degree(rad - b.to_rad()),
85        Angle::Hour(hr) => Angle::Degree(hr - b.to_hr()),
86    }
87});
88
89/// Enum representing sign of number
90#[derive(Debug, PartialEq, Eq, Clone)]
91pub enum Sign {
92    Positive,
93    Negative,
94}
95
96/// Sexagesimal Degree
97#[derive(Debug, PartialEq, Clone)]
98pub struct DegMinSec(
99    /// Sign of angle
100    pub Sign,
101    /// Degrees
102    pub u32,
103    /// Arc minutes
104    pub u32,
105    /// Arc seconds
106    pub f64,
107);
108
109/// Sexagesimal Hour
110#[derive(Debug, PartialEq, Clone)]
111pub struct HourMinSec(
112    /// Sign of angle
113    pub Sign,
114    /// Hours
115    pub u32,
116    /// Minutes
117    pub u32,
118    /// Seconds
119    pub f64,
120);
121
122/**
123Angle to sexagesimal angle implementation
124
125> NOTE: this is a macro rather than a public trait so I can implement a type trait without repeating myself for multiple types.
126
127*/
128macro_rules! impl_arc_minute_second {
129    ($T:ty) => {
130        impl $T {
131            /**
132            Decimal angle to Arc Minute Second struct.
133            */
134            pub fn angle_to_ams(decimal_angle: f64) -> Self {
135                let major: u32 = decimal_angle as u32;
136                let min: f64 = (decimal_angle - (major as f64)) * 60.0;
137                let second: f64 = (min - (min as u32 as f64)) * 60.0;
138                Self(
139                    if decimal_angle.is_sign_positive() {
140                        Sign::Positive
141                    } else {
142                        Sign::Negative
143                    },
144                    major,
145                    min as u32,
146                    second,
147                )
148            }
149        }
150    };
151}
152
153impl_arc_minute_second!(DegMinSec);
154impl_arc_minute_second!(HourMinSec);
155
156impl From<Angle> for DegMinSec {
157    fn from(angle: Angle) -> Self {
158        Self::angle_to_ams(angle.to_deg())
159    }
160}
161
162impl From<Angle> for HourMinSec {
163    fn from(angle: Angle) -> Self {
164        Self::angle_to_ams(angle.to_hr())
165    }
166}
167
168impl From<DegMinSec> for Angle {
169    fn from(angle: DegMinSec) -> Angle {
170        let angle_abs: f64 = angle.1 as f64 + angle.2 as f64 / 60.0 + angle.3 / 3600.0;
171        match angle.0 {
172            Sign::Positive => Angle::Degree(angle_abs),
173            Sign::Negative => Angle::Degree(-angle_abs),
174        }
175    }
176}
177
178impl From<HourMinSec> for Angle {
179    fn from(angle: HourMinSec) -> Angle {
180        let angle_abs: f64 = angle.1 as f64 + angle.2 as f64 / 60.0 + angle.3 / 3600.0;
181        match angle.0 {
182            Sign::Positive => Angle::Hour(angle_abs),
183            Sign::Negative => Angle::Hour(-angle_abs),
184        }
185    }
186}
187
188#[cfg(test)]
189mod tests {
190    use crate::angle::*;
191
192    const PI_THIRD: f64 = PI / 3.0;
193    const PI_SIXTH: f64 = PI / 6.0;
194
195    #[test]
196    fn angle_enum() {
197        // converting between types
198        assert_float_absolute_eq!(
199            Angle::Degree(90.0).to_deg(),
200            Angle::Radian(PI_HALF).to_deg()
201        );
202        assert_float_absolute_eq!(Angle::Degree(90.0).to_deg(), Angle::Hour(6.0).to_deg());
203        assert_float_absolute_eq!(Angle::Hour(6.0).to_deg(), Angle::Radian(PI_HALF).to_deg());
204        assert_float_absolute_eq!(Angle::Hour(6.0).to_deg(), Angle::Degree(90.0).to_deg());
205        assert_float_absolute_eq!(Angle::Radian(PI_HALF).to_deg(), Angle::Hour(6.0).to_deg());
206        assert_float_absolute_eq!(
207            Angle::Radian(PI_HALF).to_deg(),
208            Angle::Degree(90.0).to_deg()
209        );
210
211        // trigonometry
212        assert_float_absolute_eq!(1.0, Angle::Degree(0.0).cos());
213        assert_float_absolute_eq!(1.0, Angle::Hour(0.0).cos());
214        assert_float_absolute_eq!(1.0, Angle::Radian(0.0).cos());
215
216        assert_float_absolute_eq!(0.0, Angle::Degree(90.0).cos());
217        assert_float_absolute_eq!(0.0, Angle::Hour(6.0).cos());
218        assert_float_absolute_eq!(0.0, Angle::Radian(PI_HALF).cos());
219
220        assert_float_absolute_eq!(0.5, Angle::Degree(60.0).cos());
221        assert_float_absolute_eq!(0.5, Angle::Hour(4.0).cos());
222        assert_float_absolute_eq!(0.5, Angle::Radian(PI_THIRD).cos());
223
224        assert_float_absolute_eq!(2_f64.sqrt() / 2.0, Angle::Degree(45.0).cos());
225        assert_float_absolute_eq!(2_f64.sqrt() / 2.0, Angle::Hour(3.0).cos());
226        assert_float_absolute_eq!(2_f64.sqrt() / 2.0, Angle::Radian(PI_FOURTH).cos());
227
228        assert_float_absolute_eq!(0.0, Angle::Degree(0.0).sin());
229        assert_float_absolute_eq!(0.0, Angle::Hour(0.0).sin());
230        assert_float_absolute_eq!(0.0, Angle::Radian(0.0).sin());
231
232        assert_float_absolute_eq!(1.0, Angle::Degree(90.0).sin());
233        assert_float_absolute_eq!(1.0, Angle::Hour(6.0).sin());
234        assert_float_absolute_eq!(1.0, Angle::Radian(PI_HALF).sin());
235
236        assert_float_absolute_eq!(3_f64.sqrt() / 2.0, Angle::Degree(60.0).sin());
237        assert_float_absolute_eq!(3_f64.sqrt() / 2.0, Angle::Hour(4.0).sin());
238        assert_float_absolute_eq!(3_f64.sqrt() / 2.0, Angle::Radian(PI_THIRD).sin());
239
240        assert_float_absolute_eq!(2_f64.sqrt() / 2.0, Angle::Degree(45.0).sin());
241        assert_float_absolute_eq!(2_f64.sqrt() / 2.0, Angle::Hour(3.0).sin());
242        assert_float_absolute_eq!(2_f64.sqrt() / 2.0, Angle::Radian(PI_FOURTH).sin());
243
244        assert_float_absolute_eq!(0.5, Angle::Degree(30.0).sin());
245        assert_float_absolute_eq!(0.5, Angle::Hour(2.0).sin());
246        assert_float_absolute_eq!(0.5, Angle::Radian(PI_SIXTH).sin());
247
248        assert_float_absolute_eq!(1.0, Angle::Degree(45.0).tan());
249        assert_float_absolute_eq!(1.0, Angle::Hour(3.0).tan());
250        assert_float_absolute_eq!(1.0, Angle::Radian(PI_FOURTH).tan());
251    }
252
253    #[test]
254    fn arc_min_sec() {
255        // DegMinSec
256        assert_eq!(
257            DegMinSec::from(Angle::Degree(0.0)),
258            DegMinSec(Sign::Positive, 0, 0, 0.0)
259        );
260        assert_eq!(
261            DegMinSec::from(Angle::Degree(-0.0)),
262            DegMinSec(Sign::Negative, 0, 0, 0.0)
263        );
264        assert_eq!(
265            DegMinSec::from(Angle::Degree(180.0)),
266            DegMinSec(Sign::Positive, 180, 0, 0.0)
267        );
268        assert_eq!(
269            DegMinSec::from(Angle::Radian(0.0)),
270            DegMinSec(Sign::Positive, 0, 0, 0.0)
271        );
272        assert_eq!(
273            DegMinSec::from(Angle::Radian(-0.0)),
274            DegMinSec(Sign::Negative, 0, 0, 0.0)
275        );
276        assert_eq!(
277            DegMinSec::from(Angle::Radian(PI)),
278            DegMinSec(Sign::Positive, 180, 0, 0.0)
279        );
280        assert_eq!(
281            DegMinSec::from(Angle::Hour(12.0)),
282            DegMinSec(Sign::Positive, 180, 0, 0.0)
283        );
284        // HourMinSec
285        assert_eq!(
286            HourMinSec::from(Angle::Degree(0.0)),
287            HourMinSec(Sign::Positive, 0, 0, 0.0)
288        );
289        assert_eq!(
290            HourMinSec::from(Angle::Degree(-0.0)),
291            HourMinSec(Sign::Negative, 0, 0, 0.0)
292        );
293        assert_eq!(
294            HourMinSec::from(Angle::Hour(12.0)),
295            HourMinSec(Sign::Positive, 12, 0, 0.0)
296        );
297        assert_eq!(
298            HourMinSec::from(Angle::Radian(0.0)),
299            HourMinSec(Sign::Positive, 0, 0, 0.0)
300        );
301        assert_eq!(
302            HourMinSec::from(Angle::Radian(-0.0)),
303            HourMinSec(Sign::Negative, 0, 0, 0.0)
304        );
305        assert_eq!(
306            HourMinSec::from(Angle::Radian(PI)),
307            HourMinSec(Sign::Positive, 12, 0, 0.0)
308        );
309        assert_eq!(
310            HourMinSec::from(Angle::Degree(180.0)),
311            HourMinSec(Sign::Positive, 12, 0, 0.0)
312        );
313    }
314}