use proj4rs::Proj;
pub struct Crs {
proj: Proj,
}
#[derive(Debug)]
pub enum CrsError {
Proj(proj4rs::errors::Error),
Wkt(alloc::string::String),
}
impl core::fmt::Display for CrsError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
CrsError::Proj(e) => write!(f, "invalid CRS definition: {e}"),
CrsError::Wkt(m) => write!(f, "invalid WKT: {m}"),
}
}
}
impl std::error::Error for CrsError {}
impl Crs {
pub fn from_proj_string(s: &str) -> Result<Self, CrsError> {
Proj::from_proj_string(s)
.map(|proj| Self { proj })
.map_err(CrsError::Proj)
}
pub fn from_epsg(code: u16) -> Result<Self, CrsError> {
Proj::from_epsg_code(code)
.map(|proj| Self { proj })
.map_err(CrsError::Proj)
}
pub fn from_wkt(wkt: &str) -> Result<Self, CrsError> {
let projstr =
proj4wkt::wkt_to_projstring(wkt).map_err(|e| CrsError::Wkt(alloc::format!("{e:?}")))?;
Self::from_proj_string(&projstr)
}
#[must_use]
pub(crate) fn proj(&self) -> &Proj {
&self.proj
}
#[must_use]
pub fn is_geographic(&self) -> bool {
self.proj.is_latlong()
}
}
#[cfg(test)]
mod tests {
use super::Crs;
#[test]
fn build_from_epsg() {
assert!(Crs::from_epsg(4326).is_ok());
assert!(Crs::from_epsg(3857).is_ok());
}
#[test]
fn build_from_proj_string() {
assert!(Crs::from_proj_string("+proj=longlat +datum=WGS84 +no_defs").is_ok());
}
#[test]
fn wgs84_is_geographic() {
let wgs84 = Crs::from_epsg(4326).unwrap();
assert!(wgs84.is_geographic());
let mercator = Crs::from_epsg(3857).unwrap();
assert!(!mercator.is_geographic());
}
#[test]
fn bad_epsg_errors() {
assert!(Crs::from_epsg(1).is_err());
}
#[test]
fn build_from_wkt_geographic() {
let wkt = r#"GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["degree",0.0174532925199433]]"#;
let crs = Crs::from_wkt(wkt).unwrap();
assert!(crs.is_geographic());
}
#[test]
fn error_variants_display() {
let wkt_err = Crs::from_wkt("NOT WKT AT ALL")
.err()
.expect("malformed WKT is rejected");
let msg = alloc::format!("{wkt_err}");
assert!(msg.starts_with("invalid WKT:"), "got: {msg}");
let proj_err = Crs::from_proj_string("+proj=definitely_not_a_projection")
.err()
.expect("bad projection string is rejected");
let msg = alloc::format!("{proj_err}");
assert!(msg.starts_with("invalid CRS definition:"), "got: {msg}");
}
}