#![warn(missing_docs)]
#![allow(dead_code)]
use crate::{MyError, config::config};
use core::fmt;
use proj::Proj;
use std::{num::NonZero, ops::RangeInclusive};
use tracing::info;
#[derive(Debug)]
struct EoV {
x_range: RangeInclusive<f64>,
y_range: RangeInclusive<f64>,
}
#[derive(Debug)]
#[allow(clippy::upper_case_acronyms)]
pub struct CRS {
definition: String,
inner: Proj,
extent_of_validity: EoV,
}
impl fmt::Display for CRS {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.definition)
}
}
impl Default for CRS {
fn default() -> Self {
let default_crs = config().default_crs();
info!("Will try using '{default_crs}' as the default CRS...");
Self::new(default_crs).expect("Failed instantiating default CRS")
}
}
impl CRS {
pub fn new(code: &str) -> Result<Self, MyError> {
let inner = Proj::new(code)?;
let definition = code.into();
let (mb_eov, _mb_def) = inner.area_of_use()?;
let eov = mb_eov.expect("CRSes w/ no known Area-of-Use are not supported. Abort");
let extent_of_validity = EoV {
x_range: RangeInclusive::new(eov.west, eov.east),
y_range: RangeInclusive::new(eov.south, eov.north),
};
let crs = CRS {
definition,
inner,
extent_of_validity,
};
Ok(crs)
}
pub fn from_epsg(code: NonZero<usize>) -> Result<Self, MyError> {
Self::new(&format!("EPSG:{code}"))
}
pub fn check_point(&self, coord: &[f64]) -> Result<(), MyError> {
let (x, y) = (&coord[0], &coord[1]);
if !self.extent_of_validity.x_range.contains(x) {
return Err(MyError::Runtime(
"Point x (longitude) coordinate is out-of-bounds".into(),
));
}
if !self.extent_of_validity.y_range.contains(y) {
return Err(MyError::Runtime(
"Point y (latitude) coordinate is out-of-bounds".into(),
));
}
Ok(())
}
pub fn check_line(&self, coord: &[Vec<f64>]) -> Result<(), MyError> {
if coord.iter().all(|xy| self.check_point(xy).is_ok()) {
Ok(())
} else {
Err(MyError::Runtime(
"One or more line coordinates are out-of-bounds".into(),
))
}
}
pub fn check_polygon(&self, rings: &[Vec<Vec<f64>>]) -> Result<(), MyError> {
if rings.iter().all(|r| self.check_line(r).is_ok()) {
Ok(())
} else {
Err(MyError::Runtime(
"One or more polygon coordinates are out-of-bounds".into(),
))
}
}
}
#[cfg(test)]
mod tests {
use proj::Proj;
#[test]
fn test_name() {
let epsg_4326 = Proj::new("EPSG:4326").unwrap();
let (aou, _) = epsg_4326.area_of_use().unwrap();
if let Some(a) = aou {
assert_eq!(a.west, -180.0);
assert_eq!(a.east, 180.0);
assert_eq!(a.south, -90.0);
assert_eq!(a.north, 90.0);
} else {
panic!("Failed getting Area of Use for EPSG:4326")
}
}
}