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 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 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 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 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 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 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 let mut digit_buffer = [b' '; 6];
136 let blank_index = 6 - precision.num_digits() as usize;
137
138 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 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 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 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 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 pub fn value(&self) -> f64 {
217 self.0
218 }
219
220 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 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
275fn 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}