celestial_time/sidereal/
angle.rs1use celestial_core::math::fmod;
2use std::fmt;
3
4#[cfg(feature = "serde")]
5use serde::{Deserialize, Serialize};
6
7#[derive(Debug, Clone, Copy, PartialEq)]
8#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
9pub struct SiderealAngle {
10 angle_hours: f64,
11 exact_radians: Option<f64>,
12}
13
14impl SiderealAngle {
15 pub fn from_hours(hours: f64) -> Self {
16 Self {
17 angle_hours: Self::normalize_hours(hours),
18 exact_radians: None,
19 }
20 }
21
22 pub fn from_degrees(degrees: f64) -> Self {
23 Self::from_hours(degrees / 15.0)
24 }
25
26 pub fn from_radians(radians: f64) -> Self {
27 Self::from_hours(radians * 12.0 / celestial_core::constants::PI)
28 }
29
30 pub(crate) fn from_radians_exact(radians: f64) -> Self {
31 let hours = radians * 12.0 / celestial_core::constants::PI;
32 Self {
33 angle_hours: Self::normalize_hours(hours),
34 exact_radians: Some(radians),
35 }
36 }
37
38 pub fn hours(&self) -> f64 {
39 self.angle_hours
40 }
41
42 pub fn degrees(&self) -> f64 {
43 self.angle_hours * 15.0
44 }
45
46 pub fn radians(&self) -> f64 {
47 if let Some(exact) = self.exact_radians {
48 exact
49 } else {
50 self.angle_hours * celestial_core::constants::PI / 12.0
51 }
52 }
53
54 fn normalize_hours(hours: f64) -> f64 {
55 let mut normalized = fmod(hours, 24.0);
56 if normalized < 0.0 {
57 normalized += 24.0;
58 }
59 normalized
60 }
61
62 pub fn hour_angle_to_target(&self, target_ra_hours: f64) -> f64 {
63 self.hours() - target_ra_hours
64 }
65}
66
67impl fmt::Display for SiderealAngle {
68 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
69 write!(f, "{:.6}h", self.angle_hours)
70 }
71}
72
73#[cfg(test)]
74mod tests {
75 use super::*;
76
77 #[test]
78 fn test_angle_conversions() {
79 let angle = SiderealAngle::from_hours(6.0);
80
81 assert_eq!(angle.hours(), 6.0);
82 assert_eq!(angle.degrees(), 90.0);
83 assert!((angle.radians() - celestial_core::constants::HALF_PI).abs() < 1e-15);
84 }
85
86 #[test]
87 fn test_normalization() {
88 let angle1 = SiderealAngle::from_hours(25.5);
89 assert_eq!(angle1.hours(), 1.5);
90
91 let angle2 = SiderealAngle::from_hours(-1.5);
92 assert_eq!(angle2.hours(), 22.5);
93 }
94
95 #[test]
96 fn test_hour_angle_calculation() {
97 let lst = SiderealAngle::from_hours(12.0);
98 let target_ra = 6.0;
99 let hour_angle = lst.hour_angle_to_target(target_ra);
100 assert_eq!(hour_angle, 6.0);
101 }
102}