starstuff_types/
coord.rs

1/*!
2Coordinate Systems
3
4> See [converting astronomical coordinates](https://en.wikipedia.org/wiki/Astronomical_coordinate_systems#Converting_coordinates) for more details.
5 */
6
7use crate::angle::{Angle, PI, PI_FOURTH, PI_HALF};
8use crate::time::GMST;
9
10/// Trait for constrained angles
11pub trait ConstrainedAngle {
12    /// Angle constructor that panics when the underlying angle does not satisfy constraints.
13    fn new(angle: &Angle) -> Self;
14    /// Get underlying angle
15    fn value(&self) -> Angle;
16}
17
18/**
19Zenith Angle
20
21<https://en.wikipedia.org/wiki/Zenith>
22 */
23#[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/**
40Declination
41
42<https://en.wikipedia.org/wiki/Declination>
43 */
44#[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/**
61Altitude
62
63<https://en.wikipedia.org/wiki/Horizontal_coordinate_system>
64 */
65#[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/**
82Latitude
83
84<https://en.wikipedia.org/wiki/Latitude>
85 */
86#[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/**
103Right Ascension
104
105<https://en.wikipedia.org/wiki/Right_ascension>
106 */
107#[derive(Debug, Copy, Clone)]
108pub struct RightAscension(pub Angle);
109
110/**
111Azimuth
112
113<https://en.wikipedia.org/wiki/Azimuth>
114 */
115#[derive(Debug, Copy, Clone)]
116pub struct Azimuth(pub Angle);
117
118/**
119Longitude
120
121<https://en.wikipedia.org/wiki/Longitude>
122 */
123#[derive(Debug, Copy, Clone)]
124pub struct Longitude(pub Angle);
125
126/**
127Cartesian Coordinates
128
129<https://en.wikipedia.org/wiki/Cartesian_coordinate_system>
130 */
131#[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/**
140Geographic Coordinates (Latitude/Longitude)
141
142<https://en.wikipedia.org/wiki/Geographic_coordinate_system>
143 */
144#[derive(Debug, Copy, Clone)]
145pub struct Geographic {
146    pub latitude: Latitude,
147    pub longitude: Longitude,
148}
149
150/**
151Two-dimensional Polar Coordinates
152
153<https://en.wikipedia.org/wiki/Polar_coordinate_system>
154 */
155#[derive(Debug, Copy, Clone)]
156#[allow(dead_code)]
157pub struct Polar {
158    pub radius: f64,
159    pub angle: Angle,
160}
161
162/**
163Equitorial Astronomical Coordinates
164
165<https://en.wikipedia.org/wiki/Astronomical_coordinate_systems#Equatorial_system>
166 */
167#[derive(Debug, Copy, Clone)]
168pub struct Equitorial {
169    pub right_ascension: RightAscension,
170    pub declination: Declination,
171}
172
173/**
174Horizontal Astronomical Coordinates
175
176<https://en.wikipedia.org/wiki/Astronomical_coordinate_systems#Horizontal_system>
177 */
178#[derive(Debug, Copy, Clone)]
179pub struct Horizontal {
180    pub altitude: Altitude,
181    pub azimuth: Azimuth,
182}
183
184impl Horizontal {
185    /**
186    Convert equitorial coordinates to horizontal given a place and time.
187
188    <https://en.wikipedia.org/wiki/Astronomical_coordinate_systems#Equatorial_%E2%86%94_horizontal>
189     */
190    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    /**
208    Stereographic map projection of coordinates.
209
210    <https://en.wikipedia.org/wiki/Stereographic_map_projection>
211     */
212    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}