1pub struct FieldReader<'a> {
11 fields: &'a [&'a str],
12 idx: usize,
13}
14
15impl<'a> FieldReader<'a> {
16 pub fn new(fields: &'a [&'a str]) -> Self {
17 Self { fields, idx: 0 }
18 }
19
20 pub fn f32(&mut self) -> Option<f32> {
22 let val = self.fields.get(self.idx).and_then(|f| {
23 if f.is_empty() {
24 None
25 } else {
26 f.parse::<f32>().ok()
27 }
28 });
29 self.idx += 1;
30 val
31 }
32
33 pub fn f64(&mut self) -> Option<f64> {
35 let val = self.fields.get(self.idx).and_then(|f| {
36 if f.is_empty() {
37 None
38 } else {
39 f.parse::<f64>().ok()
40 }
41 });
42 self.idx += 1;
43 val
44 }
45
46 pub fn u8(&mut self) -> Option<u8> {
48 let val = self.fields.get(self.idx).and_then(|f| {
49 if f.is_empty() {
50 None
51 } else {
52 f.parse::<u8>().ok()
53 }
54 });
55 self.idx += 1;
56 val
57 }
58
59 pub fn u32(&mut self) -> Option<u32> {
61 let val = self.fields.get(self.idx).and_then(|f| {
62 if f.is_empty() {
63 None
64 } else {
65 f.parse::<u32>().ok()
66 }
67 });
68 self.idx += 1;
69 val
70 }
71
72 pub fn i8(&mut self) -> Option<i8> {
74 let val = self.fields.get(self.idx).and_then(|f| {
75 if f.is_empty() {
76 None
77 } else {
78 f.parse::<i8>().ok()
79 }
80 });
81 self.idx += 1;
82 val
83 }
84
85 pub fn char(&mut self) -> Option<char> {
87 let val = self
88 .fields
89 .get(self.idx)
90 .and_then(|f| f.chars().next().filter(|_| !f.is_empty()));
91 self.idx += 1;
92 val
93 }
94
95 pub fn string(&mut self) -> Option<String> {
97 let val = self.fields.get(self.idx).and_then(|f| {
98 if f.is_empty() {
99 None
100 } else {
101 Some((*f).to_string())
102 }
103 });
104 self.idx += 1;
105 val
106 }
107
108 pub fn skip(&mut self) {
110 self.idx += 1;
111 }
112}
113
114pub struct FieldWriter {
118 fields: Vec<String>,
119}
120
121impl FieldWriter {
122 pub fn new() -> Self {
123 Self { fields: Vec::new() }
124 }
125
126 pub fn f32(&mut self, value: Option<f32>) {
128 self.fields.push(match value {
129 Some(v) => format!("{v}"),
130 None => String::new(),
131 });
132 }
133
134 pub fn f64(&mut self, value: Option<f64>) {
136 self.fields.push(match value {
137 Some(v) => format!("{v}"),
138 None => String::new(),
139 });
140 }
141
142 pub fn u8(&mut self, value: Option<u8>) {
144 self.fields.push(match value {
145 Some(v) => v.to_string(),
146 None => String::new(),
147 });
148 }
149
150 pub fn i8(&mut self, value: Option<i8>) {
152 self.fields.push(match value {
153 Some(v) => v.to_string(),
154 None => String::new(),
155 });
156 }
157
158 pub fn u32(&mut self, value: Option<u32>) {
160 self.fields.push(match value {
161 Some(v) => v.to_string(),
162 None => String::new(),
163 });
164 }
165
166 pub fn char(&mut self, value: Option<char>) {
168 self.fields.push(match value {
169 Some(c) => c.to_string(),
170 None => String::new(),
171 });
172 }
173
174 pub fn fixed(&mut self, c: char) {
176 self.fields.push(c.to_string());
177 }
178
179 pub fn string(&mut self, value: Option<&str>) {
181 self.fields.push(value.unwrap_or("").to_string());
182 }
183
184 pub fn finish(self) -> Vec<String> {
186 self.fields
187 }
188}
189
190impl Default for FieldWriter {
191 fn default() -> Self {
192 Self::new()
193 }
194}
195
196pub trait NmeaEncodable {
219 const SENTENCE_TYPE: &str;
221
222 const PROPRIETARY_ID: &str = "";
225
226 fn encode(&self) -> Vec<String>;
228
229 fn to_sentence(&self, talker: &str) -> String {
231 let fields = self.encode();
232 let field_refs: Vec<&str> = fields.iter().map(|s| s.as_str()).collect();
233 crate::encode_frame('$', talker, Self::SENTENCE_TYPE, &field_refs)
234 }
235
236 fn to_proprietary_sentence(&self) -> String {
241 let fields = self.encode();
242 let field_refs: Vec<&str> = fields.iter().map(|s| s.as_str()).collect();
243 crate::encode_frame('$', "", Self::PROPRIETARY_ID, &field_refs)
244 }
245}
246
247pub fn ddmm_to_decimal(ddmm: f64) -> f64 {
263 let degrees = (ddmm / 100.0).floor();
264 let minutes = ddmm - degrees * 100.0;
265 degrees + minutes / 60.0
266}
267
268pub fn decimal_to_ddmm(decimal: f64) -> f64 {
283 let degrees = decimal.floor();
284 let minutes = (decimal - degrees) * 60.0;
285 degrees * 100.0 + minutes
286}
287
288#[cfg(test)]
289mod tests {
290 use super::*;
291
292 #[test]
293 fn reader_char() {
294 let fields = &["T", "", "AB"];
295 let mut r = FieldReader::new(fields);
296 assert_eq!(r.char(), Some('T'));
297 assert_eq!(r.char(), None);
298 assert_eq!(r.char(), Some('A')); }
300
301 #[test]
302 fn reader_f32() {
303 let fields = &["270.0", "", "abc"];
304 let mut r = FieldReader::new(fields);
305 assert_eq!(r.f32(), Some(270.0));
306 assert_eq!(r.f32(), None);
307 assert_eq!(r.f32(), None); }
309
310 #[test]
311 fn reader_past_end() {
312 let fields: &[&str] = &[];
313 let mut r = FieldReader::new(fields);
314 assert_eq!(r.f32(), None);
315 assert_eq!(r.char(), None);
316 }
317
318 #[test]
319 fn reader_skip() {
320 let fields = &["10.0", "T", "20.0"];
321 let mut r = FieldReader::new(fields);
322 assert_eq!(r.f32(), Some(10.0));
323 r.skip();
324 assert_eq!(r.f32(), Some(20.0));
325 }
326
327 #[test]
328 fn reader_string() {
329 let fields = &["DEST", ""];
330 let mut r = FieldReader::new(fields);
331 assert_eq!(r.string(), Some("DEST".to_string()));
332 assert_eq!(r.string(), None);
333 }
334
335 #[test]
336 fn writer_roundtrip() {
337 let mut w = FieldWriter::new();
338 w.f32(Some(270.0));
339 w.fixed('T');
340 w.f32(None);
341 w.fixed('M');
342 let fields = w.finish();
343 assert_eq!(fields, vec!["270", "T", "", "M"]);
344 }
345
346 #[test]
347 fn ddmm_to_decimal_lat() {
348 let result = ddmm_to_decimal(4807.038);
350 assert!((result - 48.1173).abs() < 0.0001);
351 }
352
353 #[test]
354 fn ddmm_to_decimal_lon() {
355 let result = ddmm_to_decimal(1131.0);
357 assert!((result - 11.5167).abs() < 0.0001);
358 }
359
360 #[test]
361 fn decimal_to_ddmm_lat() {
362 let result = decimal_to_ddmm(48.1173);
364 assert!((result - 4807.038).abs() < 0.001);
365 }
366
367 #[test]
368 fn decimal_to_ddmm_roundtrip() {
369 let original = 5132.5200_f64;
370 let roundtrip = decimal_to_ddmm(ddmm_to_decimal(original));
371 assert!((roundtrip - original).abs() < 0.0001);
372 }
373}