use crate::datum::Datum;
use crate::error::{Error, Result};
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct LinearUnit {
meters_per_unit: f64,
}
impl LinearUnit {
pub const fn metre() -> Self {
Self {
meters_per_unit: 1.0,
}
}
pub const fn meter() -> Self {
Self::metre()
}
pub const fn kilometre() -> Self {
Self {
meters_per_unit: 1000.0,
}
}
pub const fn kilometer() -> Self {
Self::kilometre()
}
pub const fn foot() -> Self {
Self {
meters_per_unit: 0.3048,
}
}
pub const fn us_survey_foot() -> Self {
Self {
meters_per_unit: 0.3048006096012192,
}
}
pub fn from_meters_per_unit(meters_per_unit: f64) -> Result<Self> {
if !meters_per_unit.is_finite() || meters_per_unit <= 0.0 {
return Err(Error::InvalidDefinition(
"linear unit conversion factor must be a finite positive number".into(),
));
}
Ok(Self { meters_per_unit })
}
pub const fn meters_per_unit(self) -> f64 {
self.meters_per_unit
}
pub const fn to_meters(self, value: f64) -> f64 {
value * self.meters_per_unit
}
pub const fn from_meters(self, value: f64) -> f64 {
value / self.meters_per_unit
}
}
#[derive(Debug, Clone, Copy)]
pub enum CrsDef {
Geographic(GeographicCrsDef),
Projected(ProjectedCrsDef),
}
impl CrsDef {
pub fn datum(&self) -> &Datum {
match self {
CrsDef::Geographic(g) => g.datum(),
CrsDef::Projected(p) => p.datum(),
}
}
pub fn epsg(&self) -> u32 {
match self {
CrsDef::Geographic(g) => g.epsg(),
CrsDef::Projected(p) => p.epsg(),
}
}
pub fn name(&self) -> &str {
match self {
CrsDef::Geographic(g) => g.name(),
CrsDef::Projected(p) => p.name(),
}
}
pub fn is_geographic(&self) -> bool {
matches!(self, CrsDef::Geographic(_))
}
pub fn is_projected(&self) -> bool {
matches!(self, CrsDef::Projected(_))
}
}
#[derive(Debug, Clone, Copy)]
pub struct GeographicCrsDef {
epsg: u32,
datum: Datum,
name: &'static str,
}
impl GeographicCrsDef {
pub const fn new(epsg: u32, datum: Datum, name: &'static str) -> Self {
Self { epsg, datum, name }
}
pub const fn epsg(&self) -> u32 {
self.epsg
}
pub const fn datum(&self) -> &Datum {
&self.datum
}
pub const fn name(&self) -> &'static str {
self.name
}
}
#[derive(Debug, Clone, Copy)]
pub struct ProjectedCrsDef {
epsg: u32,
datum: Datum,
method: ProjectionMethod,
linear_unit: LinearUnit,
name: &'static str,
}
impl ProjectedCrsDef {
pub const fn new(
epsg: u32,
datum: Datum,
method: ProjectionMethod,
linear_unit: LinearUnit,
name: &'static str,
) -> Self {
Self {
epsg,
datum,
method,
linear_unit,
name,
}
}
pub const fn epsg(&self) -> u32 {
self.epsg
}
pub const fn datum(&self) -> &Datum {
&self.datum
}
pub const fn method(&self) -> ProjectionMethod {
self.method
}
pub const fn linear_unit(&self) -> LinearUnit {
self.linear_unit
}
pub const fn linear_unit_to_meter(&self) -> f64 {
self.linear_unit.meters_per_unit()
}
pub const fn name(&self) -> &'static str {
self.name
}
}
#[derive(Debug, Clone, Copy)]
pub enum ProjectionMethod {
WebMercator,
TransverseMercator {
lon0: f64,
lat0: f64,
k0: f64,
false_easting: f64,
false_northing: f64,
},
PolarStereographic {
lon0: f64,
lat_ts: f64,
k0: f64,
false_easting: f64,
false_northing: f64,
},
LambertConformalConic {
lon0: f64,
lat0: f64,
lat1: f64,
lat2: f64,
false_easting: f64,
false_northing: f64,
},
AlbersEqualArea {
lon0: f64,
lat0: f64,
lat1: f64,
lat2: f64,
false_easting: f64,
false_northing: f64,
},
Mercator {
lon0: f64,
lat_ts: f64,
k0: f64,
false_easting: f64,
false_northing: f64,
},
EquidistantCylindrical {
lon0: f64,
lat_ts: f64,
false_easting: f64,
false_northing: f64,
},
}
#[cfg(test)]
mod tests {
use super::*;
use crate::datum;
#[test]
fn geographic_crs_is_geographic() {
let crs = CrsDef::Geographic(GeographicCrsDef::new(4326, datum::WGS84, "WGS 84"));
assert!(crs.is_geographic());
assert!(!crs.is_projected());
assert_eq!(crs.epsg(), 4326);
}
#[test]
fn projected_crs_is_projected() {
let crs = CrsDef::Projected(ProjectedCrsDef::new(
3857,
datum::WGS84,
ProjectionMethod::WebMercator,
LinearUnit::metre(),
"WGS 84 / Pseudo-Mercator",
));
assert!(crs.is_projected());
assert!(!crs.is_geographic());
assert_eq!(crs.epsg(), 3857);
}
#[test]
fn linear_unit_validates_positive_finite_conversion() {
assert!(LinearUnit::from_meters_per_unit(0.3048).is_ok());
assert!(LinearUnit::from_meters_per_unit(0.0).is_err());
assert!(LinearUnit::from_meters_per_unit(f64::NAN).is_err());
}
}