1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
use std::fmt;

pub trait Angle<T> {
    /// Converts the given angle to degrees
    fn to_degrees(&self) -> Degrees<T>;

    /// Converts the given angle to radians
    fn to_radians(&self) -> Radians<T>;
}

/// Represents an angle in degrees
#[derive(Debug, Clone, Copy)]
pub struct Degrees<T> {
    pub degrees: T,
}

impl<T: fmt::Display> fmt::Display for Degrees<T> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        self.degrees.fmt(f)?;
        write!(f, "°")
    }
}

impl From<f64> for Degrees<f64> {
    fn from(degrees: f64) -> Self {
        Self { degrees }
    }
}

impl Angle<f64> for Degrees<f64> {
    fn to_degrees(&self) -> Degrees<f64> {
        *self
    }

    fn to_radians(&self) -> Radians<f64> {
        Radians::from(self.degrees * std::f64::consts::PI / 180.0)
    }
}

/// Represents an angle in minutes of arc
#[derive(Debug, Clone, Copy)]
pub struct ArcMinutes<T> {
    pub minutes: T,
}

impl<T: fmt::Display> fmt::Display for ArcMinutes<T> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        self.minutes.fmt(f)?;
        write!(f, "’")
    }
}

impl From<f64> for ArcMinutes<f64> {
    fn from(minutes: f64) -> Self {
        Self { minutes }
    }
}

impl Angle<f64> for ArcMinutes<f64> {
    fn to_degrees(&self) -> Degrees<f64> {
        Degrees::from(self.minutes / 60.0)
    }

    fn to_radians(&self) -> Radians<f64> {
        Radians::from(self.minutes * std::f64::consts::PI / 10800.0)
    }
}

/// Represents an angle in seconds of arc
#[derive(Debug, Clone, Copy)]
pub struct ArcSeconds<T> {
    pub seconds: T,
}

impl<T: fmt::Display> fmt::Display for ArcSeconds<T> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        self.seconds.fmt(f)?;
        write!(f, "”")
    }
}

impl From<f64> for ArcSeconds<f64> {
    fn from(seconds: f64) -> Self {
        Self { seconds }
    }
}

impl Angle<f64> for ArcSeconds<f64> {
    fn to_degrees(&self) -> Degrees<f64> {
        Degrees::from(self.seconds / 3600.0)
    }

    fn to_radians(&self) -> Radians<f64> {
        Radians::from(self.seconds * std::f64::consts::PI / 648000.0)
    }
}

/// Represents an angle in radians
#[derive(Debug, Clone, Copy)]
pub struct Radians<T> {
    pub radians: T,
}

impl From<f64> for Radians<f64> {
    fn from(radians: f64) -> Self {
        Self { radians }
    }
}

impl Angle<f64> for Radians<f64> {
    fn to_degrees(&self) -> Degrees<f64> {
        Degrees::from(self.radians * 180.0 * std::f64::consts::FRAC_1_PI)
    }

    fn to_radians(&self) -> Radians<f64> {
        *self
    }

    // fn to_degrees_minutes_seconds(self) -> DMS<f64> {
    //     todo!()
    // }
}

/// Represents an angle in degrees, minutes and seconds
#[derive(Clone, Copy, Debug)]
pub struct Dms<T> {
    pub degrees: Degrees<T>,
    pub minutes: ArcMinutes<T>,
    pub seconds: ArcSeconds<T>,
}

impl Dms<f64> {
    pub fn new(degrees: f64, minutes: f64, seconds: f64) -> Self {
        Self {
            degrees: Degrees::from(degrees),
            minutes: ArcMinutes::from(minutes),
            seconds: ArcSeconds::from(seconds),
        }
    }
}

impl Angle<f64> for Dms<f64> {
    fn to_degrees(&self) -> Degrees<f64> {
        Degrees::from(
            self.degrees.degrees
                + self.minutes.to_degrees().degrees
                + self.seconds.to_degrees().degrees,
        )
    }

    fn to_radians(&self) -> Radians<f64> {
        self.to_degrees().to_radians()
    }
}

impl<T: fmt::Display> fmt::Display for Dms<T> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{}{}{}", self.degrees, self.minutes, self.seconds)
    }
}

/// Represents an angle in hours, minutes and seconds. Note that 1 hours is equal to 15 degrees.
#[derive(Clone, Copy, Debug)]
pub struct Hms<T> {
    pub hours: T,
    pub minutes: T,
    pub seconds: T,
}

impl Angle<f64> for Hms<f64> {
    fn to_degrees(&self) -> Degrees<f64> {
        let total_hours = self.hours + self.minutes / 60.0 + self.seconds / 3600.0;
        Degrees::from(total_hours * 15.0)
    }

    fn to_radians(&self) -> Radians<f64> {
        self.to_degrees().to_radians()
    }
}

impl<T: fmt::Display> fmt::Display for Hms<T> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{}h{}m{}s", self.hours, self.minutes, self.seconds)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn hms_test() {
        let value = Hms {
            hours: 9.0,
            minutes: 14.0,
            seconds: 55.8,
        };
        assert_eq!(value.to_string(), "9h14m55.8s");
        crate::assert_approx_equal(value.to_radians().radians.tan(), -0.877517);
    }

    #[test]
    fn dms_test() {
        let value = Dms::new(8.0, 5.0, 9.0);
        assert_eq!(value.to_string(), "8°5’9”");
    }
}