ogn_parser/
lonlat.rs

1use serde::Serialize;
2
3use crate::AprsError;
4use crate::EncodeError;
5use std::ops::Deref;
6use std::ops::DerefMut;
7use std::str::FromStr;
8
9#[derive(Debug, Copy, Clone, PartialOrd, PartialEq, Default, Serialize)]
10pub struct Latitude(f64);
11
12impl Deref for Latitude {
13    type Target = f64;
14
15    fn deref(&self) -> &Self::Target {
16        &self.0
17    }
18}
19
20impl DerefMut for Latitude {
21    fn deref_mut(&mut self) -> &mut Self::Target {
22        &mut self.0
23    }
24}
25
26impl FromStr for Latitude {
27    type Err = AprsError;
28
29    fn from_str(s: &str) -> Result<Self, Self::Err> {
30        let b = s.as_bytes();
31
32        if b.len() != 8 || b[4] as char != '.' {
33            return Err(Self::Err::InvalidLatitude(s.to_owned()));
34        }
35
36        let north = match b[7] as char {
37            'N' => true,
38            'S' => false,
39            _ => return Err(Self::Err::InvalidLatitude(s.to_owned())),
40        };
41
42        let deg = s[0..2]
43            .parse::<u32>()
44            .map_err(|_| Self::Err::InvalidLatitude(s.to_owned()))? as f64;
45        let min = s[2..4]
46            .parse::<u32>()
47            .map_err(|_| Self::Err::InvalidLatitude(s.to_owned()))? as f64;
48        let min_frac = s[5..7]
49            .parse::<u32>()
50            .map_err(|_| Self::Err::InvalidLatitude(s.to_owned()))? as f64;
51
52        let value = deg + min / 60. + min_frac / 6_000.;
53        let value = if north { value } else { -value };
54
55        if value > 90. || value < -90. {
56            return Err(Self::Err::InvalidLatitude(s.to_owned()));
57        }
58
59        Ok(Self(value))
60    }
61}
62
63#[derive(Debug, Copy, Clone, PartialOrd, PartialEq, Default, Serialize)]
64pub struct Longitude(f64);
65
66impl Deref for Longitude {
67    type Target = f64;
68
69    fn deref(&self) -> &Self::Target {
70        &self.0
71    }
72}
73
74impl DerefMut for Longitude {
75    fn deref_mut(&mut self) -> &mut Self::Target {
76        &mut self.0
77    }
78}
79
80impl FromStr for Longitude {
81    type Err = AprsError;
82
83    fn from_str(s: &str) -> Result<Self, Self::Err> {
84        let b = s.as_bytes();
85
86        if b.len() != 9 || b[5] as char != '.' {
87            return Err(Self::Err::InvalidLongitude(s.to_owned()));
88        }
89
90        let east = match b[8] as char {
91            'E' => true,
92            'W' => false,
93            _ => return Err(Self::Err::InvalidLongitude(s.to_owned())),
94        };
95
96        let deg = s[0..3]
97            .parse::<u32>()
98            .map_err(|_| Self::Err::InvalidLongitude(s.to_owned()))? as f64;
99        let min = s[3..5]
100            .parse::<u32>()
101            .map_err(|_| Self::Err::InvalidLongitude(s.to_owned()))? as f64;
102        let min_frac = s[6..8]
103            .parse::<u32>()
104            .map_err(|_| Self::Err::InvalidLongitude(s.to_owned()))? as f64;
105
106        let value = deg + min / 60. + min_frac / 6_000.;
107        let value = if east { value } else { -value };
108
109        if value > 180. || value < -180. {
110            return Err(Self::Err::InvalidLongitude(s.to_owned()));
111        }
112
113        Ok(Self(value))
114    }
115}
116
117pub fn encode_latitude(lat: Latitude) -> Result<String, EncodeError> {
118    let lat = lat.0;
119
120    if !(-90.0..=90.0).contains(&lat) {
121        return Err(EncodeError::InvalidLatitude(lat));
122    }
123
124    let (dir, lat) = if lat >= 0.0 { ('N', lat) } else { ('S', -lat) };
125
126    let deg = lat as u32;
127    let min = ((lat - (deg as f64)) * 60.0) as u32;
128    let min_frac = ((lat - (deg as f64) - (min as f64 / 60.0)) * 6000.0).round() as u32;
129
130    Ok(format!("{:02}{:02}.{:02}{}", deg, min, min_frac, dir))
131}
132
133pub fn encode_longitude(lon: Longitude) -> Result<String, EncodeError> {
134    let lon = lon.0;
135
136    if !(-180.0..=180.0).contains(&lon) {
137        return Err(EncodeError::InvalidLongitude(lon));
138    }
139
140    let (dir, lon) = if lon >= 0.0 { ('E', lon) } else { ('W', -lon) };
141
142    let deg = lon as u32;
143    let min = ((lon - (deg as f64)) * 60.0) as u32;
144    let min_frac = ((lon - (deg as f64) - (min as f64 / 60.0)) * 6000.0).round() as u32;
145
146    Ok(format!("{:03}{:02}.{:02}{}", deg, min, min_frac, dir))
147}
148
149#[cfg(test)]
150mod tests {
151    use super::*;
152
153    #[test]
154    fn test_latitude() {
155        assert_relative_eq!(*"4903.50N".parse::<Latitude>().unwrap(), 49.05833333333333);
156        assert_relative_eq!(*"4903.50S".parse::<Latitude>().unwrap(), -49.05833333333333);
157        assert_eq!(
158            "4903.50W".parse::<Latitude>(),
159            Err(AprsError::InvalidLatitude("4903.50W".to_owned()))
160        );
161        assert_eq!(
162            "4903.50E".parse::<Latitude>(),
163            Err(AprsError::InvalidLatitude("4903.50E".to_owned()))
164        );
165        assert_eq!(
166            "9903.50N".parse::<Latitude>(),
167            Err(AprsError::InvalidLatitude("9903.50N".to_owned()))
168        );
169        assert_relative_eq!(*"0000.00N".parse::<Latitude>().unwrap(), 0.0);
170        assert_relative_eq!(*"0000.00S".parse::<Latitude>().unwrap(), 0.0);
171    }
172
173    #[test]
174    fn test_longitude() {
175        assert_relative_eq!(
176            *"12903.50E".parse::<Longitude>().unwrap(),
177            129.05833333333334
178        );
179        assert_relative_eq!(
180            *"04903.50W".parse::<Longitude>().unwrap(),
181            -49.05833333333333
182        );
183        assert_eq!(
184            "04903.50N".parse::<Longitude>(),
185            Err(AprsError::InvalidLongitude("04903.50N".to_owned()))
186        );
187        assert_eq!(
188            "04903.50S".parse::<Longitude>(),
189            Err(AprsError::InvalidLongitude("04903.50S".to_owned()))
190        );
191        assert_eq!(
192            "18903.50E".parse::<Longitude>(),
193            Err(AprsError::InvalidLongitude("18903.50E".to_owned()))
194        );
195        assert_relative_eq!(*"00000.00E".parse::<Longitude>().unwrap(), 0.0);
196        assert_relative_eq!(*"00000.00W".parse::<Longitude>().unwrap(), 0.0);
197    }
198
199    #[test]
200    fn test_deref_mut() {
201        let mut lat = "4903.50N".parse::<Latitude>().unwrap();
202        let mut lon = "12903.50E".parse::<Longitude>().unwrap();
203        *lat -= 1.0;
204        *lon -= 1.0;
205        assert_relative_eq!(*lat, 48.05833333333333);
206        assert_relative_eq!(*lon, 128.05833333333334);
207    }
208
209    #[test]
210    fn test_encode_latitude() {
211        assert_eq!(
212            encode_latitude(Latitude(49.05833333333333)),
213            Ok("4903.50N".to_string())
214        );
215        assert_eq!(
216            encode_latitude(Latitude(-49.05833333333333)),
217            Ok("4903.50S".to_string())
218        );
219        assert_eq!(
220            encode_latitude(Latitude(-90.1)),
221            Err(EncodeError::InvalidLatitude(-90.1))
222        );
223        assert_eq!(
224            encode_latitude(Latitude(90.1)),
225            Err(EncodeError::InvalidLatitude(90.1))
226        );
227        assert_eq!(encode_latitude(Latitude(0.0)), Ok("0000.00N".to_string()));
228    }
229
230    #[test]
231    fn test_encode_longitude() {
232        assert_eq!(
233            encode_longitude(Longitude(129.05833333333334)),
234            Ok("12903.50E".to_string())
235        );
236        assert_eq!(
237            encode_longitude(Longitude(-49.05833333333333)),
238            Ok("04903.50W".to_string())
239        );
240        assert_eq!(
241            encode_longitude(Longitude(-180.1)),
242            Err(EncodeError::InvalidLongitude(-180.1))
243        );
244        assert_eq!(
245            encode_longitude(Longitude(180.1)),
246            Err(EncodeError::InvalidLongitude(180.1))
247        );
248        assert_eq!(
249            encode_longitude(Longitude(0.0)),
250            Ok("00000.00E".to_string())
251        );
252    }
253}