1use crate::angle::{Angle, PI, PI_FOURTH, PI_HALF};
8use crate::time::GMST;
9
10pub trait ConstrainedAngle {
12 fn new(angle: &Angle) -> Self;
14 fn value(&self) -> Angle;
16}
17
18#[derive(Debug, Copy, Clone)]
24pub struct ZenithAngle(Angle);
25
26impl ConstrainedAngle for ZenithAngle {
27 fn new(angle: &Angle) -> Self {
28 let rad = angle.to_rad();
29 if !(0.0..=PI).contains(&rad) {
30 panic!("ZenithAngle must be between 0 and pi!")
31 }
32 Self(*angle)
33 }
34 fn value(&self) -> Angle {
35 self.0
36 }
37}
38
39#[derive(Debug, Copy, Clone)]
45pub struct Declination(pub Angle);
46
47impl ConstrainedAngle for Declination {
48 fn new(angle: &Angle) -> Self {
49 let rad = angle.to_rad();
50 if !(-PI_HALF..=PI_HALF).contains(&rad) {
51 panic!("Declination must be between -pi/2 and pi/2!")
52 }
53 Self(*angle)
54 }
55 fn value(&self) -> Angle {
56 self.0
57 }
58}
59
60#[derive(Debug, Copy, Clone)]
66pub struct Altitude(pub Angle);
67
68impl ConstrainedAngle for Altitude {
69 fn new(angle: &Angle) -> Self {
70 let rad = angle.to_rad();
71 if !(-PI_HALF..=PI_HALF).contains(&rad) {
72 panic!("Altitude must be between -pi/2 and pi/2!")
73 }
74 Self(*angle)
75 }
76 fn value(&self) -> Angle {
77 self.0
78 }
79}
80
81#[derive(Debug, Copy, Clone)]
87pub struct Latitude(pub Angle);
88
89impl ConstrainedAngle for Latitude {
90 fn new(angle: &Angle) -> Self {
91 let rad = angle.to_rad();
92 if !(-PI_HALF..=PI_HALF).contains(&rad) {
93 panic!("Latitude must be between -pi/2 and pi/2!")
94 }
95 Self(*angle)
96 }
97 fn value(&self) -> Angle {
98 self.0
99 }
100}
101
102#[derive(Debug, Copy, Clone)]
108pub struct RightAscension(pub Angle);
109
110#[derive(Debug, Copy, Clone)]
116pub struct Azimuth(pub Angle);
117
118#[derive(Debug, Copy, Clone)]
124pub struct Longitude(pub Angle);
125
126#[derive(Debug, Copy, Clone)]
132#[allow(dead_code)]
133pub struct Cartesian {
134 pub x: f64,
135 pub y: f64,
136 pub z: f64,
137}
138
139#[derive(Debug, Copy, Clone)]
145pub struct Geographic {
146 pub latitude: Latitude,
147 pub longitude: Longitude,
148}
149
150#[derive(Debug, Copy, Clone)]
156#[allow(dead_code)]
157pub struct Polar {
158 pub radius: f64,
159 pub angle: Angle,
160}
161
162#[derive(Debug, Copy, Clone)]
168pub struct Equitorial {
169 pub right_ascension: RightAscension,
170 pub declination: Declination,
171}
172
173#[derive(Debug, Copy, Clone)]
179pub struct Horizontal {
180 pub altitude: Altitude,
181 pub azimuth: Azimuth,
182}
183
184impl Horizontal {
185 pub fn from_equitorial(eq: &Equitorial, geo: &Geographic, sidereal_time: &GMST) -> Self {
191 let hour_local: Angle = sidereal_time.0 + geo.longitude.0 - eq.right_ascension.0;
192 let x_horiz: f64 = -(geo.latitude.0.sin()) * (eq.declination.0.cos()) * (hour_local.cos())
193 + geo.latitude.0.cos() * (eq.declination.0.sin());
194 let y_horiz: f64 = eq.declination.0.cos() * hour_local.sin();
195 let azimuth_rad: Angle = Angle::Radian(-(y_horiz.atan2(x_horiz)));
196 let altitude_rad: Angle = Angle::Radian(
197 (geo.latitude.0.sin() * eq.declination.0.sin()
198 + geo.latitude.0.cos() * eq.declination.0.cos() * hour_local.cos())
199 .asin(),
200 );
201 Self {
202 altitude: Altitude(azimuth_rad),
203 azimuth: Azimuth(altitude_rad),
204 }
205 }
206
207 pub fn stereo_project(&self) -> Polar {
213 Polar {
214 radius: 2.0 * (PI_FOURTH - self.altitude.0.to_rad() / 2.0).tan(),
215 angle: self.azimuth.0,
216 }
217 }
218}