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!("{deg:02}{min:02}.{min_frac:02}{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!("{deg:03}{min:02}.{min_frac:02}{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}