aprs_parser/
lonlat.rs

1use std::io::Write;
2use std::ops::Deref;
3
4use base91;
5use bytes::parse_bytes;
6use DecodeError;
7use EncodeError;
8use Precision;
9
10#[derive(Debug, Copy, Clone, PartialOrd, PartialEq, Default)]
11pub struct Latitude(f64);
12
13impl Deref for Latitude {
14    type Target = f64;
15
16    fn deref(&self) -> &Self::Target {
17        &self.0
18    }
19}
20
21impl Latitude {
22    /// Creates a new `Latitude`.
23    /// Returns `None` if the given value is not a valid latitude.
24    pub fn new(value: f64) -> Option<Self> {
25        if value > 90.0 || value < -90.0 || value.is_nan() {
26            None
27        } else {
28            Some(Self(value))
29        }
30    }
31
32    /// Creates a new `Latitude` from degrees, minutes, and hundredths of a minute,
33    /// as well as direction
34    pub fn from_dmh(deg: u32, min: u32, hundredths: u32, north: bool) -> Option<Self> {
35        let value = f64::from(deg) + f64::from(min) / 60. + f64::from(hundredths) / 6_000.;
36        let value = if north { value } else { -value };
37
38        Self::new(value)
39    }
40
41    /// Returns the `Latitude`'s degrees, minutes, hundredths of a minute, and direction.
42    /// `true` is north, `false` is south.
43    pub fn dmh(&self) -> (u32, u32, u32, bool) {
44        let lat = self.0;
45
46        let (dir, lat) = if lat >= 0.0 {
47            (true, lat)
48        } else {
49            (false, -lat)
50        };
51
52        let mut deg = lat as u32;
53        let mut min = ((lat - (deg as f64)) * 60.0) as u32;
54        let mut hundredths = ((lat - (deg as f64) - (min as f64 / 60.0)) * 6000.0).round() as u32;
55
56        if hundredths == 100 {
57            // overflow from the rounding. need to propagate it up
58            hundredths = 0;
59            min += 1;
60        }
61
62        if min == 60 {
63            min = 0;
64            deg += 1;
65        }
66
67        (deg, min, hundredths, dir)
68    }
69
70    /// The value of the latitude.
71    pub fn value(&self) -> f64 {
72        self.0
73    }
74
75    pub(crate) fn parse_uncompressed(b: &[u8]) -> Result<(Self, Precision), DecodeError> {
76        if b.len() != 8 || b[4] != b'.' {
77            return Err(DecodeError::InvalidLatitude(b.to_owned()));
78        }
79
80        let north = match b[7] {
81            b'N' => true,
82            b'S' => false,
83            _ => return Err(DecodeError::InvalidLatitude(b.to_owned())),
84        };
85
86        // Some APRS lats have trailing spaces
87        // This is used to convey ambiguity and is only valid in latitudes
88        // Once we encounter a space, the remainder must be spaces
89        let mut total_spaces = 0;
90        let (deg, num_spaces) = parse_bytes_trailing_spaces(&[b[0], b[1]], false)
91            .ok_or_else(|| DecodeError::InvalidLatitude(b.to_owned()))?;
92        total_spaces += num_spaces;
93        let (min, num_spaces) = parse_bytes_trailing_spaces(&[b[2], b[3]], num_spaces > 0)
94            .ok_or_else(|| DecodeError::InvalidLatitude(b.to_owned()))?;
95        total_spaces += num_spaces;
96        let (min_frac, num_spaces) = parse_bytes_trailing_spaces(&[b[5], b[6]], num_spaces > 0)
97            .ok_or_else(|| DecodeError::InvalidLatitude(b.to_owned()))?;
98        total_spaces += num_spaces;
99
100        let precision = Precision::from_num_digits(total_spaces)
101            .ok_or_else(|| DecodeError::InvalidLatitude(b.to_owned()))?;
102
103        let lat = Self::from_dmh(deg, min, min_frac, north)
104            .ok_or_else(|| DecodeError::InvalidLatitude(b.to_owned()))?;
105
106        Ok((lat, precision))
107    }
108
109    pub(crate) fn parse_compressed(b: &[u8]) -> Result<Self, DecodeError> {
110        let value = 90.0
111            - (base91::decode_ascii(b)
112                .ok_or_else(|| DecodeError::InvalidLatitude(b.to_owned()))?
113                / 380926.0);
114
115        Self::new(value).ok_or_else(|| DecodeError::InvalidLatitude(b.to_owned()))
116    }
117
118    pub(crate) fn encode_compressed<W: Write>(&self, buf: &mut W) -> Result<(), EncodeError> {
119        let value = (90.0 - self.0) * 380926.0;
120        base91::encode_ascii(value, buf, 4)
121    }
122
123    pub(crate) fn encode_uncompressed<W: Write>(
124        &self,
125        buf: &mut W,
126        precision: Precision,
127    ) -> Result<(), EncodeError> {
128        let (deg, min, min_frac, is_north) = self.dmh();
129        let dir = if is_north { 'N' } else { 'S' };
130
131        // zero out fields as required for precision
132        // Ideally we would be doing some clever rounding here
133        // E.g. if last 2 digits were blanked,
134        // 4905.83 would become 4906.__
135        let mut digit_buffer = [b' '; 6];
136        let blank_index = 6 - precision.num_digits() as usize;
137
138        // write will only fail if there isn't enough space
139        // which is what we want (the remaining buffer should remain untouched)
140        let _ = write!(
141            &mut digit_buffer[..blank_index],
142            "{:02}{:02}{:02}",
143            deg,
144            min,
145            min_frac
146        );
147        buf.write_all(&digit_buffer[0..4])?;
148        write!(buf, ".")?;
149        buf.write_all(&digit_buffer[4..6])?;
150        write!(buf, "{}", dir)?;
151        Ok(())
152    }
153}
154
155#[derive(Debug, Copy, Clone, PartialOrd, PartialEq, Default)]
156pub struct Longitude(f64);
157
158impl Deref for Longitude {
159    type Target = f64;
160
161    fn deref(&self) -> &Self::Target {
162        &self.0
163    }
164}
165
166impl Longitude {
167    /// Creates a new `Longitude`.
168    /// Returns `None` if the given value is not a valid longitude
169    pub fn new(value: f64) -> Option<Self> {
170        if value > 180.0 || value < -180.0 || value.is_nan() {
171            None
172        } else {
173            Some(Self(value))
174        }
175    }
176
177    /// Creates a new `Longitude` from degrees, minutes, and hundredths of a minute,
178    /// as well as direction
179    pub fn from_dmh(deg: u32, min: u32, hundredths: u32, east: bool) -> Option<Self> {
180        let value = f64::from(deg) + f64::from(min) / 60. + f64::from(hundredths) / 6_000.;
181        let value = if east { value } else { -value };
182
183        Self::new(value)
184    }
185
186    /// Returns the `Longitude`'s degrees, minutes, hundredths of a minute, and direction.
187    /// `true` is east, `false` is west.
188    pub fn dmh(&self) -> (u32, u32, u32, bool) {
189        let lon = self.0;
190
191        let (dir, lon) = if lon >= 0.0 {
192            (true, lon)
193        } else {
194            (false, -lon)
195        };
196
197        let mut deg = lon as u32;
198        let mut min = ((lon - (deg as f64)) * 60.0) as u32;
199        let mut hundredths = ((lon - (deg as f64) - (min as f64 / 60.0)) * 6000.0).round() as u32;
200
201        if hundredths == 100 {
202            // overflow from the rounding. need to propagate it up
203            hundredths = 0;
204            min += 1;
205        }
206
207        if min == 60 {
208            min = 0;
209            deg += 1;
210        }
211
212        (deg, min, hundredths, dir)
213    }
214
215    /// The value of the longitude.
216    pub fn value(&self) -> f64 {
217        self.0
218    }
219
220    /// Precision is needed so we know how many digits to ignore
221    pub(crate) fn parse_uncompressed(b: &[u8], precision: Precision) -> Result<Self, DecodeError> {
222        if b.len() != 9 || b[5] != b'.' {
223            return Err(DecodeError::InvalidLongitude(b.to_owned()));
224        }
225
226        let east = match b[8] {
227            b'E' => true,
228            b'W' => false,
229            _ => return Err(DecodeError::InvalidLongitude(b.to_owned())),
230        };
231
232        let mut digit_buffer = [0; 7];
233        digit_buffer[0..5].copy_from_slice(&b[0..5]);
234        digit_buffer[5..7].copy_from_slice(&b[6..8]);
235
236        // zero out the digits we don't care about
237        for i in (7 - precision.num_digits())..7 {
238            digit_buffer[i as usize] = b'0';
239        }
240
241        let deg = parse_bytes::<u32>(&digit_buffer[0..3])
242            .ok_or_else(|| DecodeError::InvalidLongitude(b.to_owned()))?;
243        let min = parse_bytes::<u32>(&digit_buffer[3..5])
244            .ok_or_else(|| DecodeError::InvalidLongitude(b.to_owned()))?;
245        let min_frac = parse_bytes::<u32>(&digit_buffer[5..7])
246            .ok_or_else(|| DecodeError::InvalidLongitude(b.to_owned()))?;
247
248        Self::from_dmh(deg, min, min_frac, east)
249            .ok_or_else(|| DecodeError::InvalidLongitude(b.to_owned()))
250    }
251
252    pub(crate) fn parse_compressed(b: &[u8]) -> Result<Self, DecodeError> {
253        let value = (base91::decode_ascii(b)
254            .ok_or_else(|| DecodeError::InvalidLongitude(b.to_owned()))?
255            / 190463.0)
256            - 180.0;
257
258        Self::new(value).ok_or_else(|| DecodeError::InvalidLongitude(b.to_owned()))
259    }
260
261    pub(crate) fn encode_compressed<W: Write>(&self, buf: &mut W) -> Result<(), EncodeError> {
262        let value = (180.0 + self.0) * 190463.0;
263        base91::encode_ascii(value, buf, 4)
264    }
265
266    pub(crate) fn encode_uncompressed<W: Write>(&self, buf: &mut W) -> Result<(), EncodeError> {
267        let (deg, min, min_frac, is_east) = self.dmh();
268        let dir = if is_east { 'E' } else { 'W' };
269
270        write!(buf, "{:03}{:02}.{:02}{}", deg, min, min_frac, dir)?;
271        Ok(())
272    }
273}
274
275// if only_spaces is true, requires that b is only spaces
276// returns the parsed value as well as the number of spaces we found
277fn parse_bytes_trailing_spaces(b: &[u8; 2], only_spaces: bool) -> Option<(u32, u8)> {
278    if only_spaces {
279        if b == &[b' ', b' '] {
280            return Some((0, 2));
281        } else {
282            return None;
283        }
284    }
285    match (b[0], b[1]) {
286        (b' ', b' ') => Some((0, 2)),
287        (_, b' ') => parse_bytes::<u32>(&b[0..1]).map(|v| (v * 10, 1)),
288        (_, _) => parse_bytes::<u32>(&b[..]).map(|v| (v, 0)),
289    }
290}
291
292#[cfg(test)]
293mod tests {
294    use super::*;
295
296    #[test]
297    fn test_latitude_out_of_bounds() {
298        assert_eq!(None, Latitude::new(90.1));
299        assert_eq!(None, Latitude::new(-90.1));
300    }
301
302    #[test]
303    fn test_longitude_out_of_bounds() {
304        assert_eq!(None, Latitude::new(180.1));
305        assert_eq!(None, Latitude::new(-180.1));
306    }
307
308    #[test]
309    fn test_parse_bytes_trailing_spaces() {
310        assert_eq!(Some((12, 0)), parse_bytes_trailing_spaces(b"12", false));
311        assert_eq!(Some((10, 1)), parse_bytes_trailing_spaces(b"1 ", false));
312        assert_eq!(Some((0, 2)), parse_bytes_trailing_spaces(b"  ", false));
313
314        assert_eq!(None, parse_bytes_trailing_spaces(b" 2", false));
315
316        assert_eq!(None, parse_bytes_trailing_spaces(b"12", true));
317        assert_eq!(None, parse_bytes_trailing_spaces(b"1 ", true));
318        assert_eq!(None, parse_bytes_trailing_spaces(b" 1", true));
319        assert_eq!(Some((0, 2)), parse_bytes_trailing_spaces(b"  ", true));
320    }
321
322    #[test]
323    fn test_parse_uncompressed_latitude() {
324        assert_eq!(
325            Latitude::parse_uncompressed(&b"4903.50N"[..]).unwrap(),
326            (
327                Latitude::new(49.05833333333333).unwrap(),
328                Precision::HundredthMinute
329            )
330        );
331        assert_eq!(
332            Latitude::parse_uncompressed(&b"4903.50S"[..]).unwrap(),
333            (
334                Latitude::new(-49.05833333333333).unwrap(),
335                Precision::HundredthMinute
336            )
337        );
338        assert_eq!(
339            Latitude::parse_uncompressed(&b"4903.5 S"[..]).unwrap(),
340            (
341                Latitude::new(-49.05833333333333).unwrap(),
342                Precision::TenthMinute
343            )
344        );
345        assert_eq!(
346            Latitude::parse_uncompressed(&b"4903.  S"[..]).unwrap(),
347            (Latitude::new(-49.05).unwrap(), Precision::OneMinute)
348        );
349        assert_eq!(
350            Latitude::parse_uncompressed(&b"490 .  S"[..]).unwrap(),
351            (Latitude::new(-49.0).unwrap(), Precision::TenMinute)
352        );
353        assert_eq!(
354            Latitude::parse_uncompressed(&b"4   .  S"[..]).unwrap(),
355            (Latitude::new(-40.0).unwrap(), Precision::TenDegree)
356        );
357        assert_eq!(
358            Latitude::parse_uncompressed(&b"    .  S"[..]),
359            Err(DecodeError::InvalidLatitude(b"    .  S".to_vec()))
360        );
361        assert_eq!(
362            Latitude::parse_uncompressed(&b"49 3.50W"[..]),
363            Err(DecodeError::InvalidLatitude(b"49 3.50W".to_vec()))
364        );
365        assert_eq!(
366            Latitude::parse_uncompressed(&b"490 .50W"[..]),
367            Err(DecodeError::InvalidLatitude(b"490 .50W".to_vec()))
368        );
369        assert_eq!(
370            Latitude::parse_uncompressed(&b"49  . 0W"[..]),
371            Err(DecodeError::InvalidLatitude(b"49  . 0W".to_vec()))
372        );
373        assert_eq!(
374            Latitude::parse_uncompressed(&b"4903.50W"[..]),
375            Err(DecodeError::InvalidLatitude(b"4903.50W".to_vec()))
376        );
377        assert_eq!(
378            Latitude::parse_uncompressed(&b"4903.50E"[..]),
379            Err(DecodeError::InvalidLatitude(b"4903.50E".to_vec()))
380        );
381        assert_eq!(
382            Latitude::parse_uncompressed(&b"9903.50N"[..]),
383            Err(DecodeError::InvalidLatitude(b"9903.50N".to_vec()))
384        );
385        assert_eq!(
386            Latitude::parse_uncompressed(&b"0000.00N"[..]).unwrap(),
387            (Latitude::new(0.0).unwrap(), Precision::HundredthMinute)
388        );
389        assert_eq!(
390            Latitude::parse_uncompressed(&b"0000.00S"[..]).unwrap(),
391            (Latitude::new(0.0).unwrap(), Precision::HundredthMinute)
392        );
393    }
394
395    #[test]
396    fn test_parse_uncompressed_longitude() {
397        assert_relative_eq!(
398            *Longitude::parse_uncompressed(&b"12903.50E"[..], Precision::default()).unwrap(),
399            129.05833333333333
400        );
401        assert_relative_eq!(
402            *Longitude::parse_uncompressed(&b"04903.50W"[..], Precision::default()).unwrap(),
403            -49.05833333333333
404        );
405        assert_eq!(
406            Longitude::parse_uncompressed(&b"04903.50N"[..], Precision::default()),
407            Err(DecodeError::InvalidLongitude(b"04903.50N".to_vec()))
408        );
409        assert_eq!(
410            Longitude::parse_uncompressed(&b"04903.50S"[..], Precision::default()),
411            Err(DecodeError::InvalidLongitude(b"04903.50S".to_vec()))
412        );
413        assert_eq!(
414            Longitude::parse_uncompressed(&b"18903.50E"[..], Precision::default()),
415            Err(DecodeError::InvalidLongitude(b"18903.50E".to_vec()))
416        );
417        assert_relative_eq!(
418            *Longitude::parse_uncompressed(&b"00000.00E"[..], Precision::default()).unwrap(),
419            0.0
420        );
421        assert_relative_eq!(
422            *Longitude::parse_uncompressed(&b"00000.00W"[..], Precision::default()).unwrap(),
423            0.0
424        );
425        assert_relative_eq!(
426            *Longitude::parse_uncompressed(&b"00000.ZZW"[..], Precision::OneMinute).unwrap(),
427            0.0
428        );
429        assert_relative_eq!(
430            *Longitude::parse_uncompressed(&b"00000.98W"[..], Precision::OneMinute).unwrap(),
431            0.0
432        );
433    }
434
435    #[test]
436    fn test_encode_uncompressed_latitude() {
437        let mut buf = vec![];
438        Latitude::new(49.05833)
439            .unwrap()
440            .encode_uncompressed(&mut buf, Precision::default())
441            .unwrap();
442        assert_eq!(buf, &b"4903.50N"[..]);
443
444        let mut buf = vec![];
445        Latitude::new(-49.05833)
446            .unwrap()
447            .encode_uncompressed(&mut buf, Precision::default())
448            .unwrap();
449        assert_eq!(buf, &b"4903.50S"[..]);
450
451        let mut buf = vec![];
452        Latitude::new(0.0)
453            .unwrap()
454            .encode_uncompressed(&mut buf, Precision::default())
455            .unwrap();
456        assert_eq!(buf, &b"0000.00N"[..]);
457
458        let mut buf = vec![];
459        Latitude::new(-49.05833)
460            .unwrap()
461            .encode_uncompressed(&mut buf, Precision::OneMinute)
462            .unwrap();
463        assert_eq!(buf, &b"4903.  S"[..]);
464    }
465
466    #[test]
467    fn test_dmh_lat() {
468        let lat = Latitude::new(11.99999999).unwrap();
469        assert_eq!((12, 0, 0, true), lat.dmh());
470
471        let lat = Latitude::new(-11.99999999).unwrap();
472        assert_eq!((12, 0, 0, false), lat.dmh());
473
474        let lat = Latitude::new(89.9999999).unwrap();
475        assert_eq!((90, 0, 0, true), lat.dmh());
476    }
477
478    #[test]
479    fn test_dmh_lon() {
480        let lon = Longitude::new(33.9999999999).unwrap();
481        assert_eq!((34, 0, 0, true), lon.dmh());
482
483        let lon = Longitude::new(-33.9999999999).unwrap();
484        assert_eq!((34, 0, 0, false), lon.dmh());
485
486        let lon = Longitude::new(179.9999999).unwrap();
487        assert_eq!((180, 0, 0, true), lon.dmh());
488    }
489
490    #[test]
491    fn test_encode_uncompressed_longitude() {
492        let mut buf = vec![];
493        Longitude::new(129.05833)
494            .unwrap()
495            .encode_uncompressed(&mut buf)
496            .unwrap();
497        assert_eq!(buf, &b"12903.50E"[..]);
498
499        let mut buf = vec![];
500        Longitude::new(-49.0583)
501            .unwrap()
502            .encode_uncompressed(&mut buf)
503            .unwrap();
504        assert_eq!(buf, &b"04903.50W"[..]);
505
506        let mut buf = vec![];
507        Longitude(0.0).encode_uncompressed(&mut buf).unwrap();
508        assert_eq!(buf, &b"00000.00E"[..]);
509    }
510}