1#![doc = include_str!("../README.md")]
2#![no_std]
3#![warn(missing_docs)]
4#![forbid(unsafe_code)]
5
6use core::fmt;
7use core::ops;
8use core::result;
9use core::str::FromStr;
10
11#[derive(Clone, Copy, Debug, PartialEq)]
12pub enum Error {
14 ParseError,
16 CoordinatesOutOfRangeError,
18}
19impl fmt::Display for Error {
20 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
21 match self {
22 Error::ParseError => write!(f, "parse error"),
23 Error::CoordinatesOutOfRangeError => write!(f, "coordinates out of range error"),
24 }
25 }
26}
27impl core::error::Error for Error {}
28
29type Result<T> = result::Result<T, Error>;
30
31const RAW_DIGIT_LIMIT: [u8; 8] = [18, 18, 10, 10, 24, 24, 10, 10];
32
33const FROM_CHAR_FNS: [fn(char) -> Result<u8>; 8] = [
34 from_char_18,
35 from_char_18,
36 from_char_10,
37 from_char_10,
38 from_char_24,
39 from_char_24,
40 from_char_10,
41 from_char_10,
42];
43
44const TO_CHAR_FNS: [fn(u32) -> char; 8] = [
45 to_char_18, to_char_18, to_char_10, to_char_10, to_char_24, to_char_24, to_char_10, to_char_10, ];
50
51#[doc(hidden)]
52pub trait GridParam: private::Sealed {
53 type UInt;
55
56 const LEN: usize;
58
59 const LON_STEP: f64;
61 const LAT_STEP: f64;
63
64 const INT_MAX: Self::UInt;
66
67 fn position_value(i: usize) -> Self::UInt;
69
70 fn internal_int(a: f64, step: f64) -> Self::UInt;
72}
73mod private {
74 pub trait Sealed {}
75
76 impl Sealed for super::Grid4Param {}
77 impl Sealed for super::Grid6Param {}
78 impl Sealed for super::Grid8Param {}
79}
80
81#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
82#[doc(hidden)]
83pub struct Grid4Param;
84impl GridParam for Grid4Param {
85 type UInt = u8;
86
87 const LEN: usize = 4;
88
89 const LON_STEP: f64 = 2.0;
90 const LAT_STEP: f64 = 1.0;
91
92 const INT_MAX: Self::UInt = 180 - 1;
93
94 fn position_value(i: usize) -> Self::UInt {
95 [10, 1][i]
96 }
97
98 fn internal_int(a: f64, step: f64) -> Self::UInt {
99 ((a / step) as u8).min(Self::INT_MAX)
104 }
105}
106
107#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
108#[doc(hidden)]
109pub struct Grid6Param;
110impl GridParam for Grid6Param {
111 type UInt = u16;
112
113 const LEN: usize = 6;
114
115 const LON_STEP: f64 = 2.0 / 24.0;
116 const LAT_STEP: f64 = 1.0 / 24.0;
117
118 const INT_MAX: Self::UInt = 180 * 24 - 1;
119
120 fn position_value(i: usize) -> Self::UInt {
121 [10 * 24, 24, 1][i]
122 }
123
124 fn internal_int(a: f64, step: f64) -> Self::UInt {
125 ((a / step) as u16).min(Self::INT_MAX)
126 }
127}
128
129#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
130#[doc(hidden)]
131pub struct Grid8Param;
132impl GridParam for Grid8Param {
133 type UInt = u16;
134
135 const LEN: usize = 8;
136
137 const LON_STEP: f64 = 2.0 / 24.0 / 10.0;
138 const LAT_STEP: f64 = 1.0 / 24.0 / 10.0;
139
140 const INT_MAX: Self::UInt = 180 * 24 * 10 - 1;
141
142 fn position_value(i: usize) -> Self::UInt {
143 [10 * 24 * 10, 24 * 10, 10, 1][i]
144 }
145
146 fn internal_int(a: f64, step: f64) -> Self::UInt {
147 ((a / step) as u16).min(Self::INT_MAX)
148 }
149}
150
151#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
152pub struct Grid<P>
154where
155 P: GridParam,
156 f64: From<P::UInt>,
157{
158 lon: P::UInt,
161 lat: P::UInt,
164}
165
166impl<P> Grid<P>
167where
168 P: GridParam,
169 P::UInt: Copy,
170 f64: From<P::UInt>,
171{
172 const LEN: usize = P::LEN;
173
174 fn position_value(i: usize) -> P::UInt {
175 P::position_value(i)
176 }
177
178 pub fn from_raw_digits(ds: &[u8]) -> Result<Grid<P>>
191 where
192 P::UInt: From<u8>,
193 P::UInt: ops::Mul<P::UInt, Output = P::UInt>,
194 P::UInt: ops::AddAssign<P::UInt>,
195 {
196 if ds.len() != Self::LEN || !ds.iter().enumerate().all(|(i, &d)| d < RAW_DIGIT_LIMIT[i]) {
197 return Err(Error::ParseError);
198 }
199
200 let mut lon = P::UInt::from(0);
201 let mut lat = P::UInt::from(0);
202 for i in 0..(Self::LEN / 2) {
203 lon += Self::position_value(i) * P::UInt::from(ds[2 * i + 0]);
204 lat += Self::position_value(i) * P::UInt::from(ds[2 * i + 1]);
205 }
206
207 Ok(Grid { lon, lat })
208 }
209
210 pub fn west_lon(&self) -> f64 {
212 (f64::from(self.lon)) * P::LON_STEP - 180.0
213 }
214 pub fn center_lon(&self) -> f64 {
216 (f64::from(self.lon) + 0.5) * P::LON_STEP - 180.0
217 }
218 pub fn east_lon(&self) -> f64 {
220 (f64::from(self.lon) + 1.0) * P::LON_STEP - 180.0
221 }
222
223 pub fn south_lat(&self) -> f64 {
225 (f64::from(self.lat)) * P::LAT_STEP - 90.0
226 }
227 pub fn center_lat(&self) -> f64 {
229 (f64::from(self.lat) + 0.5) * P::LAT_STEP - 90.0
230 }
231 pub fn north_lat(&self) -> f64 {
233 (f64::from(self.lat) + 1.0) * P::LAT_STEP - 90.0
234 }
235
236 pub fn center_lat_lon(&self) -> (f64, f64) {
246 (self.center_lat(), self.center_lon())
247 }
248
249 pub fn from_lat_lon(lat: f64, lon: f64) -> Result<Self> {
259 if -180.0 <= lon && lon < 180.0 && -90.0 <= lat && lat < 90.0 {
260 let lon = P::internal_int(lon + 180.0, P::LON_STEP);
261 let lat = P::internal_int(lat + 90.0, P::LAT_STEP);
262 Ok(Grid { lon, lat })
263 } else {
264 Err(Error::CoordinatesOutOfRangeError)
265 }
266 }
267}
268
269pub type Grid4 = Grid<Grid4Param>;
271impl Grid4 {
272 pub fn raw_digits(&self) -> [u8; Self::LEN] {
288 let mut lon = self.lon;
289 let mut lat = self.lat;
290 let mut ds = [0; Self::LEN];
291 for i in 0..(Self::LEN / 2) {
292 ds[2 * i + 0] = (lon / Self::position_value(i)) as u8;
293 ds[2 * i + 1] = (lat / Self::position_value(i)) as u8;
294
295 lon %= Self::position_value(i);
296 lat %= Self::position_value(i);
297 }
298 ds
299 }
300}
301
302impl FromStr for Grid4 {
303 type Err = Error;
304
305 fn from_str(s: &str) -> Result<Self> {
317 if s.len() != Self::LEN || !s.is_ascii() {
318 return Err(Error::ParseError);
319 }
320
321 let mut ds = [0; Self::LEN];
322 for (i, c) in s.chars().enumerate() {
323 ds[i] = FROM_CHAR_FNS[i](c)?;
324 }
325 Grid4::from_raw_digits(&ds)
326 }
327}
328
329impl fmt::Display for Grid4 {
330 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
331 let ds = self.raw_digits();
332
333 for (i, d) in ds.into_iter().enumerate() {
334 write!(f, "{}", TO_CHAR_FNS[i](d.into()))?;
335 }
336 Ok(())
337 }
338}
339
340#[allow(private_interfaces)]
342pub type Grid6 = Grid<Grid6Param>;
343impl Grid6 {
344 pub fn raw_digits(&self) -> [u8; Self::LEN] {
349 let mut lon = self.lon;
350 let mut lat = self.lat;
351 let mut ds = [0; Self::LEN];
352 for i in 0..(Self::LEN / 2) {
353 ds[2 * i + 0] = (lon / Self::position_value(i)) as u8;
354 ds[2 * i + 1] = (lat / Self::position_value(i)) as u8;
355
356 lon %= Self::position_value(i);
357 lat %= Self::position_value(i);
358 }
359 ds
360 }
361}
362
363impl FromStr for Grid6 {
364 type Err = Error;
365
366 fn from_str(s: &str) -> Result<Self> {
379 if s.len() != Self::LEN || !s.is_ascii() {
380 return Err(Error::ParseError);
381 }
382
383 let mut ds = [0; Self::LEN];
384 for (i, c) in s.chars().enumerate() {
385 ds[i] = FROM_CHAR_FNS[i](c)?;
386 }
387 Grid6::from_raw_digits(&ds)
388 }
389}
390impl fmt::Display for Grid6 {
391 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
392 let ds = self.raw_digits();
393
394 for (i, d) in ds.into_iter().enumerate() {
395 write!(f, "{}", TO_CHAR_FNS[i](d.into()))?;
396 }
397 Ok(())
398 }
399}
400
401#[allow(private_interfaces)]
403pub type Grid8 = Grid<Grid8Param>;
404impl Grid8 {
405 pub fn raw_digits(&self) -> [u8; Self::LEN] {
410 let mut lon = self.lon;
411 let mut lat = self.lat;
412 let mut ds = [0; Self::LEN];
413 for i in 0..(Self::LEN / 2) {
414 ds[2 * i + 0] = (lon / Self::position_value(i)) as u8;
415 ds[2 * i + 1] = (lat / Self::position_value(i)) as u8;
416
417 lon %= Self::position_value(i);
418 lat %= Self::position_value(i);
419 }
420 ds
421 }
422}
423
424impl FromStr for Grid8 {
425 type Err = Error;
426
427 fn from_str(s: &str) -> Result<Self> {
440 if s.len() != Self::LEN || !s.is_ascii() {
441 return Err(Error::ParseError);
442 }
443
444 let mut ds = [0; Self::LEN];
445 for (i, c) in s.chars().enumerate() {
446 ds[i] = FROM_CHAR_FNS[i](c)?;
447 }
448 Grid8::from_raw_digits(&ds)
449 }
450}
451impl fmt::Display for Grid8 {
452 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
453 let ds = self.raw_digits();
454
455 for (i, d) in ds.into_iter().enumerate() {
456 write!(f, "{}", TO_CHAR_FNS[i](d.into()))?;
457 }
458 Ok(())
459 }
460}
461
462fn from_char_18(c: char) -> Result<u8> {
463 if ('A'..='R').contains(&c) {
464 Ok((u32::from(c) - u32::from('A')) as u8)
465 } else if ('a'..='r').contains(&c) {
466 Ok((u32::from(c) - u32::from('a')) as u8)
467 } else {
468 Err(Error::ParseError)
469 }
470}
471
472fn from_char_10(c: char) -> Result<u8> {
473 if c.is_ascii_digit() {
474 Ok((u32::from(c) - u32::from('0')) as u8)
475 } else {
476 Err(Error::ParseError)
477 }
478}
479
480fn from_char_24(c: char) -> Result<u8> {
481 if ('A'..='X').contains(&c) {
482 Ok((u32::from(c) - u32::from('A')) as u8)
483 } else if ('a'..='x').contains(&c) {
484 Ok((u32::from(c) - u32::from('a')) as u8)
485 } else {
486 Err(Error::ParseError)
487 }
488}
489
490fn to_char_18(d: u32) -> char {
491 assert!(d < 18);
492 char::from_u32(u32::from('A') + d).unwrap()
493}
494
495fn to_char_10(d: u32) -> char {
496 assert!(d < 10);
497 char::from_u32(u32::from('0') + d).unwrap()
498}
499
500fn to_char_24(d: u32) -> char {
501 assert!(d < 24);
502 char::from_u32(u32::from('A') + d).unwrap()
503}
504
505#[cfg(test)]
506mod tests {
507 use super::*;
508
509 #[test]
510 fn test_grid4_from_str() {
511 let grid_min = Grid4 { lon: 0, lat: 0 };
512 let grid_max = Grid4 { lon: 179, lat: 179 };
513
514 assert_eq!(Ok(grid_min), Grid4::from_str("AA00"));
515 assert_eq!(Ok(grid_min), Grid4::from_str("aa00"));
516
517 assert_eq!(Ok(grid_max), Grid4::from_str("RR99"));
518 assert_eq!(Ok(grid_max), Grid4::from_str("rr99"));
519
520 assert_eq!(Err(Error::ParseError), Grid4::from_str("RR99xx"));
521 assert_eq!(Err(Error::ParseError), Grid4::from_str("RR99xx99"));
522
523 assert_eq!(Err(Error::ParseError), Grid4::from_str(""));
524 assert_eq!(Err(Error::ParseError), Grid4::from_str("AA0"));
525 assert_eq!(Err(Error::ParseError), Grid4::from_str("AA00 "));
526
527 assert_eq!(Err(Error::ParseError), Grid4::from_str("-A00"));
528 assert_eq!(Err(Error::ParseError), Grid4::from_str("SA00"));
529 assert_eq!(Err(Error::ParseError), Grid4::from_str("AS00"));
530 assert_eq!(Err(Error::ParseError), Grid4::from_str("A-00"));
531 assert_eq!(Err(Error::ParseError), Grid4::from_str("AA-0"));
532 assert_eq!(Err(Error::ParseError), Grid4::from_str("AAA0"));
533 assert_eq!(Err(Error::ParseError), Grid4::from_str("AA0-"));
534 assert_eq!(Err(Error::ParseError), Grid4::from_str("AA0A"));
535 }
536
537 #[test]
538 fn test_grid6_from_str() {
539 let grid_min = Grid6 { lon: 0, lat: 0 };
540 let grid_max = Grid6 {
541 lon: 4319,
542 lat: 4319,
543 };
544 assert_eq!(Ok(grid_min), Grid6::from_str("AA00aa"));
545 assert_eq!(Ok(grid_min), Grid6::from_str("AA00AA"));
546 assert_eq!(Ok(grid_min), Grid6::from_str("aa00aa"));
547
548 assert_eq!(Ok(grid_max), Grid6::from_str("RR99xx"));
549 assert_eq!(Ok(grid_max), Grid6::from_str("RR99XX"));
550 assert_eq!(Ok(grid_max), Grid6::from_str("rr99xx"));
551
552 assert_eq!(Err(Error::ParseError), Grid6::from_str("RR99"));
553 assert_eq!(Err(Error::ParseError), Grid6::from_str("RR99xx99"));
554
555 assert_eq!(Err(Error::ParseError), Grid6::from_str(""));
556 assert_eq!(Err(Error::ParseError), Grid6::from_str("AA00a"));
557 assert_eq!(Err(Error::ParseError), Grid6::from_str("AA00aa "));
558
559 assert_eq!(Err(Error::ParseError), Grid6::from_str("-A00aa"));
560 assert_eq!(Err(Error::ParseError), Grid6::from_str("SA00aa"));
561 assert_eq!(Err(Error::ParseError), Grid6::from_str("A-00aa"));
562 assert_eq!(Err(Error::ParseError), Grid6::from_str("AS00aa"));
563 assert_eq!(Err(Error::ParseError), Grid6::from_str("AA-0aa"));
564 assert_eq!(Err(Error::ParseError), Grid6::from_str("AAA0aa"));
565 assert_eq!(Err(Error::ParseError), Grid6::from_str("AA0-aa"));
566 assert_eq!(Err(Error::ParseError), Grid6::from_str("AA0Aaa"));
567 assert_eq!(Err(Error::ParseError), Grid6::from_str("AA00-a"));
568 assert_eq!(Err(Error::ParseError), Grid6::from_str("AA00ya"));
569 assert_eq!(Err(Error::ParseError), Grid6::from_str("AA00a-"));
570 assert_eq!(Err(Error::ParseError), Grid6::from_str("AA00ay"));
571 }
572
573 #[test]
574 fn test_grid8_from_str() {
575 let grid_min = Grid8 { lon: 0, lat: 0 };
576 let grid_max = Grid8 {
577 lon: 43199,
578 lat: 43199,
579 };
580 assert_eq!(Ok(grid_min), Grid8::from_str("AA00aa00"));
581 assert_eq!(Ok(grid_min), Grid8::from_str("AA00AA00"));
582 assert_eq!(Ok(grid_min), Grid8::from_str("aa00aa00"));
583
584 assert_eq!(Ok(grid_max), Grid8::from_str("RR99xx99"));
585 assert_eq!(Ok(grid_max), Grid8::from_str("RR99XX99"));
586 assert_eq!(Ok(grid_max), Grid8::from_str("rr99xx99"));
587
588 assert_eq!(Err(Error::ParseError), Grid8::from_str("RR99"));
589 assert_eq!(Err(Error::ParseError), Grid8::from_str("RR99xx"));
590
591 assert_eq!(Err(Error::ParseError), Grid8::from_str(""));
592 assert_eq!(Err(Error::ParseError), Grid8::from_str("AA00aa0"));
593 assert_eq!(Err(Error::ParseError), Grid8::from_str("AA00aa00 "));
594
595 assert_eq!(Err(Error::ParseError), Grid8::from_str("-A00aa00"));
596 assert_eq!(Err(Error::ParseError), Grid8::from_str("SA00aa00"));
597 assert_eq!(Err(Error::ParseError), Grid8::from_str("A-00aa00"));
598 assert_eq!(Err(Error::ParseError), Grid8::from_str("AS00aa00"));
599 assert_eq!(Err(Error::ParseError), Grid8::from_str("AA-0aa00"));
600 assert_eq!(Err(Error::ParseError), Grid8::from_str("AAA0aa00"));
601 assert_eq!(Err(Error::ParseError), Grid8::from_str("AA0-aa00"));
602 assert_eq!(Err(Error::ParseError), Grid8::from_str("AA0Aaa00"));
603 assert_eq!(Err(Error::ParseError), Grid8::from_str("AA00-a00"));
604 assert_eq!(Err(Error::ParseError), Grid8::from_str("AA00ya00"));
605 assert_eq!(Err(Error::ParseError), Grid8::from_str("AA00a-00"));
606 assert_eq!(Err(Error::ParseError), Grid8::from_str("AA00ay00"));
607 assert_eq!(Err(Error::ParseError), Grid8::from_str("AA00aa-0"));
608 assert_eq!(Err(Error::ParseError), Grid8::from_str("AA00aaA0"));
609 assert_eq!(Err(Error::ParseError), Grid8::from_str("AA00aa0-"));
610 assert_eq!(Err(Error::ParseError), Grid8::from_str("AA00aa0A"));
611 }
612
613 fn abs_diff(x: f64, y: f64) -> f64 {
614 (x - y).abs()
615 }
616 const EPS: f64 = 1e-12;
617
618 #[test]
619 fn test_grid4_west() {
620 assert_eq!(-180.0, Grid4::from_str("AA00").unwrap().west_lon());
621 assert_eq!(178.0, Grid4::from_str("RR99").unwrap().west_lon());
622 assert_eq!(6.0, Grid4::from_str("JO31").unwrap().west_lon());
623 }
624
625 #[test]
626 fn test_grid6_west() {
627 let lon = Grid6::from_str("AA00aa").unwrap().west_lon();
628 assert!(abs_diff(lon, -180.0) < EPS);
629
630 let lon = Grid6::from_str("RR99xx").unwrap().west_lon();
631 assert!(abs_diff(lon, 180.0 - 5.0 / 60.0) < EPS);
632
633 let lon = Grid6::from_str("JO31mk").unwrap().west_lon();
634 assert!(abs_diff(lon, 6.0 + 12.0 * 5.0 / 60.0) < EPS);
635 }
636
637 #[test]
638 fn test_grid8_west() {
639 let lon = Grid8::from_str("AA00aa00").unwrap().west_lon();
640 assert!(abs_diff(lon, -180.0) < EPS);
641
642 let lon = Grid8::from_str("RR99xx99").unwrap().west_lon();
643 assert!(abs_diff(lon, 180.0 - 30.0 / 3600.0) < EPS);
644
645 let lon = Grid8::from_str("JO31mk25").unwrap().west_lon();
646 assert!(abs_diff(lon, 6.0 + 12.0 * 5.0 / 60.0 + 2.0 * 30.0 / 3600.0) < EPS);
647 }
648
649 #[test]
650 fn test_grid4_east() {
651 assert_eq!(-178.0, Grid4::from_str("AA00").unwrap().east_lon());
652 assert_eq!(180.0, Grid4::from_str("RR99").unwrap().east_lon());
653 assert_eq!(8.0, Grid4::from_str("JO31").unwrap().east_lon());
654 }
655
656 #[test]
657 fn test_grid6_east() {
658 let lon = Grid6::from_str("AA00aa").unwrap().east_lon();
659 assert!(abs_diff(lon, -180.0 + 5.0 / 60.0) < EPS);
660
661 let lon = Grid6::from_str("RR99xx").unwrap().east_lon();
662 assert!(abs_diff(lon, 180.0) < EPS);
663
664 let lon = Grid6::from_str("JO31mk").unwrap().east_lon();
665 assert!(abs_diff(lon, 6.0 + 13.0 * 5.0 / 60.0) < EPS);
666 }
667
668 #[test]
669 fn test_grid8_east() {
670 let lon = Grid8::from_str("AA00aa00").unwrap().east_lon();
671 assert!(abs_diff(lon, -180.0 + 30.0 / 3600.0) < EPS);
672
673 let lon = Grid8::from_str("RR99xx99").unwrap().east_lon();
674 assert!(abs_diff(lon, 180.0) < EPS);
675
676 let lon = Grid8::from_str("JO31mk25").unwrap().east_lon();
677 assert!(abs_diff(lon, 6.0 + 12.0 * 5.0 / 60.0 + 3.0 * 30.0 / 3600.0) < EPS);
678 }
679
680 #[test]
681 fn test_grid4_south() {
682 assert_eq!(-90.0, Grid4::from_str("AA00").unwrap().south_lat());
683 assert_eq!(89.0, Grid4::from_str("RR99").unwrap().south_lat());
684 assert_eq!(51.0, Grid4::from_str("JO31").unwrap().south_lat());
685 }
686
687 #[test]
688 fn test_grid6_south() {
689 let lat = Grid6::from_str("AA00aa").unwrap().south_lat();
690 assert!(abs_diff(lat, -90.0) < EPS);
691
692 let lat = Grid6::from_str("RR99xx").unwrap().south_lat();
693 assert!(abs_diff(lat, 90.0 - 2.5 / 60.0) < EPS);
694
695 let lat = Grid6::from_str("JO31mk").unwrap().south_lat();
696 assert!(abs_diff(lat, 51.0 + 10.0 * 2.5 / 60.0) < EPS);
697 }
698
699 #[test]
700 fn test_grid8_south() {
701 let lat = Grid8::from_str("AA00aa00").unwrap().south_lat();
702 assert!(abs_diff(lat, -90.0) < EPS);
703
704 let lat = Grid8::from_str("RR99xx99").unwrap().south_lat();
705 assert!(abs_diff(lat, 90.0 - 15.0 / 3600.0) < EPS);
706
707 let lat = Grid8::from_str("JO31mk25").unwrap().south_lat();
708 assert!(abs_diff(lat, 51.0 + 10.0 * 2.5 / 60.0 + 5.0 * 15.0 / 3600.0) < EPS);
709 }
710
711 #[test]
712 fn test_grid4_north() {
713 assert_eq!(-89.0, Grid4::from_str("AA00").unwrap().north_lat());
714 assert_eq!(90.0, Grid4::from_str("RR99").unwrap().north_lat());
715 assert_eq!(52.0, Grid4::from_str("JO31").unwrap().north_lat());
716 }
717
718 #[test]
719 fn test_grid6_north() {
720 let lat = Grid6::from_str("AA00aa").unwrap().north_lat();
721 assert!(abs_diff(lat, -90.0 + 2.5 / 60.0) < EPS);
722
723 let lat = Grid6::from_str("RR99xx").unwrap().north_lat();
724 assert!(abs_diff(lat, 90.0) < EPS);
725
726 let lat = Grid6::from_str("JO31mk").unwrap().north_lat();
727 assert!(abs_diff(lat, 51.0 + 11.0 * 2.5 / 60.0) < EPS);
728 }
729
730 #[test]
731 fn test_grid8_north() {
732 let lat = Grid8::from_str("AA00aa00").unwrap().north_lat();
733 assert!(abs_diff(lat, -90.0 + 15.0 / 3600.0) < EPS);
734
735 let lat = Grid8::from_str("RR99xx99").unwrap().north_lat();
736 assert!(abs_diff(lat, 90.0) < EPS);
737
738 let lat = Grid8::from_str("JO31mk25").unwrap().north_lat();
739 assert!(abs_diff(lat, 51.0 + 10.0 * 2.5 / 60.0 + 6.0 * 15.0 / 3600.0) < EPS);
740 }
741
742 #[test]
743 fn test_grid4_center() {
744 let g0 = Grid4::from_str("AA00").unwrap();
745 assert_eq!((-89.5, -179.0), g0.center_lat_lon());
746
747 let g1 = Grid4::from_str("RR99").unwrap();
748 assert_eq!((89.5, 179.0), g1.center_lat_lon());
749
750 let g2 = Grid4::from_str("JO31").unwrap();
751 assert_eq!((51.5, 7.0), g2.center_lat_lon());
752 }
753
754 #[test]
755 fn test_grid6_center() {
756 let (lat, lon) = Grid6::from_str("AA00aa").unwrap().center_lat_lon();
757 assert!(abs_diff(lat, -90.0 + 1.25 / 60.0) < EPS);
758 assert!(abs_diff(lon, -180.0 + 2.5 / 60.0) < EPS);
759
760 let (lat, lon) = Grid6::from_str("RR99xx").unwrap().center_lat_lon();
761 assert!(abs_diff(lat, 90.0 - 1.25 / 60.0) < EPS);
762 assert!(abs_diff(lon, 180.0 - 2.5 / 60.0) < EPS);
763
764 let (lat, lon) = Grid6::from_str("JO31mk").unwrap().center_lat_lon();
765 assert!(abs_diff(lat, 51.0 + 10.5 * 2.5 / 60.0) < EPS);
766 assert!(abs_diff(lon, 6.0 + 12.5 * 5.0 / 60.0) < EPS);
767 }
768
769 #[test]
770 fn test_grid8_center() {
771 let (lat, lon) = Grid8::from_str("AA00aa00").unwrap().center_lat_lon();
772 assert!(abs_diff(lat, -90.0 + 7.5 / 3600.0) < EPS);
773 assert!(abs_diff(lon, -180.0 + 15.0 / 3600.0) < EPS);
774
775 let (lat, lon) = Grid8::from_str("RR99xx99").unwrap().center_lat_lon();
776 assert!(abs_diff(lat, 90.0 - 7.5 / 3600.0) < EPS);
777 assert!(abs_diff(lon, 180.0 - 15.0 / 3600.0) < EPS);
778
779 let (lat, lon) = Grid8::from_str("JO31mk25").unwrap().center_lat_lon();
780 assert!(abs_diff(lat, 51.0 + 10.0 * 2.5 / 60.0 + 5.5 * 15.0 / 3600.0) < EPS);
781 assert!(abs_diff(lon, 6.0 + 12.0 * 5.0 / 60.0 + 2.5 * 30.0 / 3600.0) < EPS);
782 }
783
784 #[test]
785 fn test_grid4_from_lat_lon() {
786 assert_eq!(
787 Ok(Grid4::from_str("AA00").unwrap()),
788 Grid4::from_lat_lon(-90.0, -180.0)
789 );
790 assert_eq!(
791 Ok(Grid4::from_str("RR99").unwrap()),
792 Grid4::from_lat_lon(89.9999, 179.9999)
793 );
794 assert_eq!(
795 Ok(Grid4::from_str("JO31").unwrap()),
796 Grid4::from_lat_lon(51.4385, 7.0249)
797 );
798
799 assert_eq!(
800 Err(Error::CoordinatesOutOfRangeError),
801 Grid4::from_lat_lon(-90.1, 0.0)
802 );
803 assert_eq!(
804 Err(Error::CoordinatesOutOfRangeError),
805 Grid4::from_lat_lon(90.1, 0.0)
806 );
807 assert_eq!(
808 Err(Error::CoordinatesOutOfRangeError),
809 Grid4::from_lat_lon(0.0, -180.1)
810 );
811 assert_eq!(
812 Err(Error::CoordinatesOutOfRangeError),
813 Grid4::from_lat_lon(0.0, 180.1)
814 );
815 }
816
817 #[test]
818 fn test_grid6_from_lat_lon() {
819 assert_eq!(
820 Ok(Grid6::from_str("AA00aa").unwrap()),
821 Grid6::from_lat_lon(-90.0, -180.0)
822 );
823 assert_eq!(
824 Ok(Grid6::from_str("RR99xx").unwrap()),
825 Grid6::from_lat_lon(89.9999, 179.9999)
826 );
827 assert_eq!(
828 Ok(Grid6::from_str("JO31mk").unwrap()),
829 Grid6::from_lat_lon(51.4385, 7.0249)
830 );
831
832 assert_eq!(
833 Err(Error::CoordinatesOutOfRangeError),
834 Grid6::from_lat_lon(-90.1, 0.0)
835 );
836 assert_eq!(
837 Err(Error::CoordinatesOutOfRangeError),
838 Grid6::from_lat_lon(90.1, 0.0)
839 );
840 assert_eq!(
841 Err(Error::CoordinatesOutOfRangeError),
842 Grid6::from_lat_lon(0.0, -180.1)
843 );
844 assert_eq!(
845 Err(Error::CoordinatesOutOfRangeError),
846 Grid6::from_lat_lon(0.0, 180.1)
847 );
848 }
849
850 #[test]
851 fn test_grid8_from_lat_lon() {
852 assert_eq!(
853 Ok(Grid8::from_str("AA00aa00").unwrap()),
854 Grid8::from_lat_lon(-90.0, -180.0)
855 );
856 assert_eq!(
857 Ok(Grid8::from_str("RR99xx99").unwrap()),
858 Grid8::from_lat_lon(89.9999, 179.9999)
859 );
860 assert_eq!(
861 Ok(Grid8::from_str("JO31mk25").unwrap()),
862 Grid8::from_lat_lon(51.4385, 7.0249)
863 );
864
865 assert_eq!(
866 Err(Error::CoordinatesOutOfRangeError),
867 Grid8::from_lat_lon(-90.1, 0.0)
868 );
869 assert_eq!(
870 Err(Error::CoordinatesOutOfRangeError),
871 Grid8::from_lat_lon(90.1, 0.0)
872 );
873 assert_eq!(
874 Err(Error::CoordinatesOutOfRangeError),
875 Grid8::from_lat_lon(0.0, -180.1)
876 );
877 assert_eq!(
878 Err(Error::CoordinatesOutOfRangeError),
879 Grid8::from_lat_lon(0.0, 180.1)
880 );
881 }
882
883 #[test]
884 fn test_grid4_from_raw_digits() {
885 assert_eq!(
886 Ok(Grid4::from_str("AA00").unwrap()),
887 Grid4::from_raw_digits(&[0, 0, 0, 0])
888 );
889 assert_eq!(
890 Ok(Grid4::from_str("RR99").unwrap()),
891 Grid4::from_raw_digits(&[17, 17, 9, 9])
892 );
893 assert_eq!(
894 Ok(Grid4::from_str("JO31").unwrap()),
895 Grid4::from_raw_digits(&[9, 14, 3, 1])
896 );
897 }
898
899 #[test]
900 fn test_grid6_from_raw_digits() {
901 assert_eq!(
902 Ok(Grid6::from_str("AA00aa").unwrap()),
903 Grid6::from_raw_digits(&[0, 0, 0, 0, 0, 0])
904 );
905 assert_eq!(
906 Ok(Grid6::from_str("RR99xx").unwrap()),
907 Grid6::from_raw_digits(&[17, 17, 9, 9, 23, 23])
908 );
909 assert_eq!(
910 Ok(Grid6::from_str("JO31mk").unwrap()),
911 Grid6::from_raw_digits(&[9, 14, 3, 1, 12, 10])
912 );
913 }
914
915 #[test]
916 fn test_grid8_from_raw_digits() {
917 assert_eq!(
918 Ok(Grid8::from_str("AA00aa00").unwrap()),
919 Grid8::from_raw_digits(&[0, 0, 0, 0, 0, 0, 0, 0])
920 );
921 assert_eq!(
922 Ok(Grid8::from_str("RR99xx99").unwrap()),
923 Grid8::from_raw_digits(&[17, 17, 9, 9, 23, 23, 9, 9])
924 );
925 assert_eq!(
926 Ok(Grid8::from_str("JO31mk25").unwrap()),
927 Grid8::from_raw_digits(&[9, 14, 3, 1, 12, 10, 2, 5])
928 );
929
930 assert_eq!(
931 Err(Error::ParseError),
932 Grid8::from_raw_digits(&[18, 0, 0, 0, 0, 0, 0, 0])
933 );
934 assert_eq!(
935 Err(Error::ParseError),
936 Grid8::from_raw_digits(&[0, 18, 0, 0, 0, 0, 0, 0])
937 );
938 assert_eq!(
939 Err(Error::ParseError),
940 Grid8::from_raw_digits(&[0, 0, 10, 0, 0, 0, 0, 0])
941 );
942 assert_eq!(
943 Err(Error::ParseError),
944 Grid8::from_raw_digits(&[0, 0, 0, 10, 0, 0, 0, 0])
945 );
946 assert_eq!(
947 Err(Error::ParseError),
948 Grid8::from_raw_digits(&[0, 0, 0, 0, 24, 0, 0, 0])
949 );
950 assert_eq!(
951 Err(Error::ParseError),
952 Grid8::from_raw_digits(&[0, 0, 0, 0, 0, 24, 0, 0])
953 );
954 assert_eq!(
955 Err(Error::ParseError),
956 Grid8::from_raw_digits(&[0, 0, 0, 0, 0, 0, 10, 0])
957 );
958 assert_eq!(
959 Err(Error::ParseError),
960 Grid8::from_raw_digits(&[0, 0, 0, 0, 0, 0, 0, 10])
961 );
962 }
963
964 #[test]
965 fn test_grid4_raw_digits() {
966 assert_eq!(
967 [0, 0, 0, 0], Grid4::from_str("AA00").unwrap().raw_digits()
969 );
970 assert_eq!(
971 [17, 17, 9, 9],
972 Grid4::from_str("RR99").unwrap().raw_digits()
973 );
974 assert_eq!(
975 [9, 14, 3, 1], Grid4::from_str("JO31").unwrap().raw_digits()
977 );
978 }
979
980 #[test]
981 fn test_grid6_raw_digits() {
982 assert_eq!(
983 [0, 0, 0, 0, 0, 0],
984 Grid6::from_str("AA00aa").unwrap().raw_digits()
985 );
986 assert_eq!(
987 [17, 17, 9, 9, 23, 23],
988 Grid6::from_str("RR99xx").unwrap().raw_digits()
989 );
990 assert_eq!(
991 [9, 14, 3, 1, 12, 10],
992 Grid6::from_str("JO31mk").unwrap().raw_digits()
993 );
994 }
995
996 #[test]
997 fn test_grid8_raw_digits() {
998 assert_eq!(
999 [0, 0, 0, 0, 0, 0, 0, 0],
1000 Grid8::from_str("AA00aa00").unwrap().raw_digits()
1001 );
1002 assert_eq!(
1003 [17, 17, 9, 9, 23, 23, 9, 9],
1004 Grid8::from_str("RR99xx99").unwrap().raw_digits()
1005 );
1006 assert_eq!(
1007 [9, 14, 3, 1, 12, 10, 2, 5],
1008 Grid8::from_str("JO31mk25").unwrap().raw_digits()
1009 );
1010 }
1011
1012 #[test]
1013 fn test_send() {
1014 fn assert_send<T: Send>() {}
1015 assert_send::<Grid4>();
1016 assert_send::<Grid6>();
1017 assert_send::<Grid8>();
1018 }
1019
1020 #[test]
1021 fn test_sync() {
1022 fn assert_sync<T: Sync>() {}
1023 assert_sync::<Grid4>();
1024 assert_sync::<Grid6>();
1025 assert_sync::<Grid8>();
1026 }
1027}