1#![warn(missing_docs)]
4#![allow(dead_code)]
5
6use crate::{MyError, config::config};
10use core::fmt;
11use proj::Proj;
12use tracing::info;
13
14#[derive(Debug)]
15struct EoV {
16 x_range: (f64, f64),
18 y_range: (f64, f64),
20}
21
22#[derive(Debug)]
24#[allow(clippy::upper_case_acronyms)]
25pub struct CRS {
26 definition: String,
27 inner: Proj,
28 extent_of_validity: EoV,
29}
30
31impl fmt::Display for CRS {
34 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
35 write!(f, "{}", self.definition)
36 }
37}
38
39impl Default for CRS {
40 fn default() -> Self {
41 let default_crs = config().default_crs();
42 info!("Will try using '{default_crs}' as the default CRS...");
43 Self::new(default_crs).expect("Failed instantiating default CRS")
44 }
45}
46
47impl CRS {
48 pub fn new(code: &str) -> Result<Self, MyError> {
52 let inner = Proj::new(code)?;
53 let definition = code.into();
54 let (mb_eov, _mb_def) = inner.area_of_use()?;
55 let eov = mb_eov.expect("CRSes w/ no known Area-of-Use are not supported. Abort");
58 let extent_of_validity = EoV {
59 x_range: (eov.west, eov.east),
60 y_range: (eov.south, eov.north),
61 };
62 let crs = CRS {
63 definition,
64 inner,
65 extent_of_validity,
66 };
67
68 Ok(crs)
71 }
72
73 pub fn check_point(&self, coord: &[f64]) -> Result<(), MyError> {
75 let (x, y) = (&coord[0], &coord[1]);
77 let (x_min, x_max) = self.extent_of_validity.x_range;
78 let x_ok = *x >= x_min && *x <= x_max;
79 if !x_ok {
80 return Err(MyError::Runtime(
81 "Point x (longitude) coordinate is out-of-bounds".into(),
82 ));
83 }
84 let (y_min, y_max) = self.extent_of_validity.y_range;
85 let y_ok = *y >= y_min && *y <= y_max;
86 if !y_ok {
87 return Err(MyError::Runtime(
88 "Point y (latitude) coordinate is out-of-bounds".into(),
89 ));
90 }
91 Ok(())
92 }
93
94 pub fn check_line(&self, coord: &[Vec<f64>]) -> Result<(), MyError> {
96 if coord.iter().all(|xy| self.check_point(xy).is_ok()) {
97 Ok(())
98 } else {
99 Err(MyError::Runtime(
100 "One or more line coordinates are out-of-bounds".into(),
101 ))
102 }
103 }
104
105 pub fn check_polygon(&self, rings: &[Vec<Vec<f64>>]) -> Result<(), MyError> {
107 if rings.iter().all(|r| self.check_line(r).is_ok()) {
108 Ok(())
109 } else {
110 Err(MyError::Runtime(
111 "One or more polygon coordinates are out-of-bounds".into(),
112 ))
113 }
114 }
115}
116
117#[cfg(test)]
118mod tests {
119 use proj::Proj;
120 use tracing_test::traced_test;
121
122 #[test]
123 #[traced_test]
124 fn test_name() {
125 let epsg_4326 = Proj::new("EPSG:4326").unwrap();
126 let (aou, _) = epsg_4326.area_of_use().unwrap();
129 if let Some(a) = aou {
132 assert_eq!(a.west, -180.0);
133 assert_eq!(a.east, 180.0);
134 assert_eq!(a.south, -90.0);
135 assert_eq!(a.north, 90.0);
136 } else {
137 panic!("Failed getting Area of Use for EPSG:4326")
138 }
139 }
140}