use crate::{ProjResult, ProjTransformError};
use elicitation::{GeoCoord, GeoGeometry, ProjArea};
use geo::MapCoords;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use tracing::instrument;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
#[serde(tag = "kind")]
pub enum ProjSpec {
ProjString {
definition: String,
},
KnownCrs {
from: String,
to: String,
area: Option<ProjArea>,
},
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub struct ProjTransform {
pub spec: ProjSpec,
}
impl ProjTransform {
#[instrument(skip(definition))]
pub fn from_proj_string(definition: impl Into<String>) -> Self {
Self {
spec: ProjSpec::ProjString {
definition: definition.into(),
},
}
}
#[instrument(skip(from, to))]
pub fn from_known_crs(
from: impl Into<String>,
to: impl Into<String>,
area: Option<ProjArea>,
) -> Self {
Self {
spec: ProjSpec::KnownCrs {
from: from.into(),
to: to.into(),
area,
},
}
}
#[instrument]
pub(crate) fn build(&self) -> ProjResult<proj::Proj> {
match &self.spec {
ProjSpec::ProjString { definition } => {
proj::Proj::new(definition).map_err(|e| ProjTransformError::create(e))
}
ProjSpec::KnownCrs { from, to, area } => {
proj::Proj::new_known_crs(from, to, area.map(|a| a.into()))
.map_err(|e| ProjTransformError::create(e))
}
}
}
#[instrument]
pub fn convert_coord(&self, coord: GeoCoord) -> ProjResult<GeoCoord> {
let p = self.build()?;
let (nx, ny) = p
.convert((coord.x, coord.y))
.map_err(|e| ProjTransformError::operation(e))?;
Ok(GeoCoord { x: nx, y: ny })
}
#[instrument]
pub fn project_coord(&self, coord: GeoCoord, inverse: bool) -> ProjResult<GeoCoord> {
let p = self.build()?;
let (nx, ny) = p
.project((coord.x, coord.y), inverse)
.map_err(|e| ProjTransformError::operation(e))?;
Ok(GeoCoord { x: nx, y: ny })
}
#[instrument(skip(geometry))]
pub fn convert_geometry(&self, geometry: GeoGeometry) -> ProjResult<GeoGeometry> {
let p = self.build()?;
let upstream: geo::Geometry<f64> = geometry.into();
let transformed = upstream
.try_map_coords(|coord| {
let (nx, ny) = p
.convert((coord.x, coord.y))
.map_err(|e| ProjTransformError::operation(e))?;
Ok(geo::Coord { x: nx, y: ny })
})
.map_err(|e: ProjTransformError| e)?;
Ok(GeoGeometry::from(transformed))
}
#[instrument]
pub fn definition(&self) -> ProjResult<String> {
self.build()?
.def()
.map_err(|e| ProjTransformError::operation(e))
}
#[instrument]
pub fn transform_bounds(
&self,
west: f64,
south: f64,
east: f64,
north: f64,
densify_pts: i32,
) -> ProjResult<[f64; 4]> {
let p = self.build()?;
p.transform_bounds(west, south, east, north, densify_pts)
.map_err(|e| ProjTransformError::operation(e))
}
}
impl elicitation::emit_code::ToCodeLiteral for ProjTransform {
fn to_code_literal(&self) -> proc_macro2::TokenStream {
let json = serde_json::to_string(self).expect("ProjTransform is always serializable");
quote::quote! {
::serde_json::from_str::<::elicit_proj::ProjTransform>(#json)
.expect("valid ProjTransform JSON")
}
}
}