aprs_parser/
compressed_cs.rs

1use std::io::Write;
2
3use base91;
4use compression_type::NmeaSource;
5use AprsCompressionType;
6use DecodeError;
7use EncodeError;
8
9#[derive(PartialEq, Copy, Clone, Debug)]
10pub enum AprsCompressedCs {
11    CourseSpeed(AprsCourseSpeed),
12    RadioRange(AprsRadioRange),
13    Altitude(AprsAltitude),
14}
15
16impl AprsCompressedCs {
17    pub(crate) fn parse(c: u8, s: u8, t: AprsCompressionType) -> Result<Self, DecodeError> {
18        let c_lwr = base91::digit_from_ascii(c).ok_or(DecodeError::InvalidCs([c, s]))?;
19        let s_lwr = base91::digit_from_ascii(s).ok_or(DecodeError::InvalidCs([c, s]))?;
20
21        if t.nmea_source == NmeaSource::Gga {
22            Ok(AprsCompressedCs::Altitude(AprsAltitude::from_cs(
23                c_lwr, s_lwr,
24            )))
25        } else {
26            let val = match c_lwr {
27                0..=89 => AprsCompressedCs::CourseSpeed(AprsCourseSpeed::from_cs(c_lwr, s_lwr)),
28                90 => AprsCompressedCs::RadioRange(AprsRadioRange::from_s(s_lwr)),
29                _ => return Err(DecodeError::InvalidCs([c, s])),
30            };
31
32            Ok(val)
33        }
34    }
35
36    pub(crate) fn encode<W: Write>(
37        self,
38        buf: &mut W,
39        t: AprsCompressionType,
40    ) -> Result<(), EncodeError> {
41        match self {
42            AprsCompressedCs::CourseSpeed(cs) => {
43                let (c, s) = cs.to_cs();
44                buf.write_all(&[base91::digit_to_ascii(c), base91::digit_to_ascii(s)])?;
45            }
46            AprsCompressedCs::RadioRange(rr) => {
47                let s = rr.to_s();
48                buf.write_all(&[b'{', base91::digit_to_ascii(s)])?;
49            }
50            AprsCompressedCs::Altitude(a) => {
51                if t.nmea_source != NmeaSource::Gga {
52                    return Err(EncodeError::NonGgaAltitude);
53                }
54
55                let (c, s) = a.to_cs();
56                buf.write_all(&[base91::digit_to_ascii(c), base91::digit_to_ascii(s)])?;
57            }
58        }
59
60        buf.write_all(&[base91::digit_to_ascii(u8::from(t))])?;
61
62        Ok(())
63    }
64}
65
66#[derive(PartialEq, Copy, Clone, Debug)]
67pub struct AprsCourseSpeed {
68    course_degrees: u16,
69    speed_knots: f64,
70}
71
72impl AprsCourseSpeed {
73    pub fn new(course_degrees: u16, speed_knots: f64) -> Self {
74        assert!(course_degrees <= 360);
75        assert!(speed_knots < (1.08_f64).powi(255));
76
77        Self {
78            course_degrees,
79            speed_knots,
80        }
81    }
82
83    pub fn course_degrees(&self) -> u16 {
84        self.course_degrees
85    }
86
87    pub fn speed_knots(&self) -> f64 {
88        self.speed_knots
89    }
90
91    fn from_cs(c: u8, s: u8) -> Self {
92        let course_degrees = c as u16 * 4;
93        let speed_knots = (1.08_f64).powi(s as i32) - 1.0;
94
95        debug_assert!(course_degrees <= 360);
96        debug_assert!(speed_knots < (1.08_f64).powi(255));
97
98        Self {
99            course_degrees,
100            speed_knots,
101        }
102    }
103
104    fn to_cs(self) -> (u8, u8) {
105        let c = self.course_degrees / 4;
106        let s = ((self.speed_knots + 1.0).ln() / (1.08_f64).ln()).round();
107
108        (c as u8, s as u8)
109    }
110}
111
112#[derive(PartialEq, Copy, Clone, Debug)]
113pub struct AprsRadioRange {
114    range_miles: f64,
115}
116
117impl AprsRadioRange {
118    pub fn new(range_miles: f64) -> Self {
119        assert!(range_miles < (1.08_f64).powi(255));
120
121        Self { range_miles }
122    }
123
124    pub fn range_miles(&self) -> f64 {
125        self.range_miles
126    }
127
128    fn from_s(s: u8) -> Self {
129        Self {
130            range_miles: 2.0 * (1.08_f64).powi(s as i32),
131        }
132    }
133
134    fn to_s(self) -> u8 {
135        let s = ((self.range_miles / 2.0).ln() / (1.08_f64).ln()).round();
136
137        s as u8
138    }
139}
140
141#[derive(PartialEq, Copy, Clone, Debug)]
142pub struct AprsAltitude {
143    altitude_feet: f64,
144}
145
146impl AprsAltitude {
147    pub fn new(altitude_feet: f64) -> Self {
148        assert!(altitude_feet < (1.002_f64).powi(255 * 91 + 255));
149
150        Self { altitude_feet }
151    }
152
153    pub fn altitude_feet(&self) -> f64 {
154        self.altitude_feet
155    }
156
157    fn from_cs(c: u8, s: u8) -> Self {
158        Self {
159            altitude_feet: (1.002_f64).powi(c as i32 * 91 + s as i32),
160        }
161    }
162
163    fn to_cs(self) -> (u8, u8) {
164        let alt = (self.altitude_feet.ln() / (1.002_f64).ln()).round() as i32;
165
166        let c = alt / 91;
167        let s = alt % 91;
168
169        (c as u8, s as u8)
170    }
171}
172
173#[cfg(test)]
174mod tests {
175    use super::*;
176
177    #[test]
178    fn course_speed_exhaustive() {
179        for c in 0..91 {
180            for s in 0..91 {
181                let val = AprsCourseSpeed::from_cs(c, s);
182
183                assert_eq!((c, s), val.to_cs());
184            }
185        }
186    }
187
188    #[test]
189    fn radio_range_exhaustive() {
190        for s in 0..91 {
191            let val = AprsRadioRange::from_s(s);
192
193            assert_eq!(s, val.to_s());
194        }
195    }
196
197    #[test]
198    fn altitude_exhaustive() {
199        for c in 0..91 {
200            for s in 0..91 {
201                let val = AprsAltitude::from_cs(c, s);
202
203                assert_eq!((c, s), val.to_cs());
204            }
205        }
206    }
207}