use crate::error::{ProjectionError, Result};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EpochPolicy {
Strict,
AllowStaticFallback,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct EpochTransformOptions {
pub coordinate_epoch_decimal_year: Option<f64>,
pub source_reference_epoch_decimal_year: Option<f64>,
pub target_reference_epoch_decimal_year: Option<f64>,
pub operation_code: Option<u32>,
pub prefer_official_operation: bool,
pub epoch_policy: EpochPolicy,
}
impl Default for EpochTransformOptions {
fn default() -> Self {
Self {
coordinate_epoch_decimal_year: None,
source_reference_epoch_decimal_year: None,
target_reference_epoch_decimal_year: None,
operation_code: None,
prefer_official_operation: true,
epoch_policy: EpochPolicy::Strict,
}
}
}
impl EpochTransformOptions {
pub fn new() -> Self {
Self::default()
}
pub fn with_coordinate_epoch(mut self, coordinate_epoch_decimal_year: f64) -> Self {
self.coordinate_epoch_decimal_year = Some(coordinate_epoch_decimal_year);
self
}
pub fn with_source_reference_epoch(mut self, source_reference_epoch_decimal_year: f64) -> Self {
self.source_reference_epoch_decimal_year = Some(source_reference_epoch_decimal_year);
self
}
pub fn with_target_reference_epoch(mut self, target_reference_epoch_decimal_year: f64) -> Self {
self.target_reference_epoch_decimal_year = Some(target_reference_epoch_decimal_year);
self
}
pub fn with_operation_code(mut self, operation_code: u32) -> Self {
self.operation_code = Some(operation_code);
self
}
pub fn with_preferred_operation(mut self, enabled: bool) -> Self {
self.prefer_official_operation = enabled;
self
}
pub fn with_epoch_policy(mut self, epoch_policy: EpochPolicy) -> Self {
self.epoch_policy = epoch_policy;
self
}
pub fn validate(&self) -> Result<()> {
if let Some(v) = self.coordinate_epoch_decimal_year {
if !v.is_finite() {
return Err(ProjectionError::DatumError(
"coordinate epoch must be finite".to_string(),
));
}
}
if let Some(v) = self.source_reference_epoch_decimal_year {
if !v.is_finite() {
return Err(ProjectionError::DatumError(
"source reference epoch must be finite".to_string(),
));
}
}
if let Some(v) = self.target_reference_epoch_decimal_year {
if !v.is_finite() {
return Err(ProjectionError::DatumError(
"target reference epoch must be finite".to_string(),
));
}
}
if self.coordinate_epoch_decimal_year.is_none()
&& (self.source_reference_epoch_decimal_year.is_some()
|| self.target_reference_epoch_decimal_year.is_some())
{
return Err(ProjectionError::DatumError(
"source/target reference epochs require coordinate epoch".to_string(),
));
}
Ok(())
}
pub fn build_context(&self) -> Result<Option<TransformEpochContext>> {
self.validate()?;
Ok(self.coordinate_epoch_decimal_year.map(|coordinate_epoch_decimal_year| {
TransformEpochContext::new(
coordinate_epoch_decimal_year,
self.source_reference_epoch_decimal_year,
self.target_reference_epoch_decimal_year,
)
}))
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct TransformEpochContext {
pub coordinate_epoch_decimal_year: f64,
pub source_reference_epoch_decimal_year: Option<f64>,
pub target_reference_epoch_decimal_year: Option<f64>,
}
impl TransformEpochContext {
pub const fn at_epoch(coordinate_epoch_decimal_year: f64) -> Self {
Self {
coordinate_epoch_decimal_year,
source_reference_epoch_decimal_year: None,
target_reference_epoch_decimal_year: None,
}
}
pub const fn new(
coordinate_epoch_decimal_year: f64,
source_reference_epoch_decimal_year: Option<f64>,
target_reference_epoch_decimal_year: Option<f64>,
) -> Self {
Self {
coordinate_epoch_decimal_year,
source_reference_epoch_decimal_year,
target_reference_epoch_decimal_year,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Point2D {
pub x: f64,
pub y: f64,
}
impl Point2D {
pub fn new(x: f64, y: f64) -> Self {
Point2D { x, y }
}
pub fn lonlat(lon: f64, lat: f64) -> Self {
Point2D { x: lon, y: lat }
}
pub fn to_tuple(self) -> (f64, f64) {
(self.x, self.y)
}
}
impl From<(f64, f64)> for Point2D {
fn from((x, y): (f64, f64)) -> Self {
Point2D::new(x, y)
}
}
impl From<Point2D> for (f64, f64) {
fn from(p: Point2D) -> Self {
(p.x, p.y)
}
}
impl std::fmt::Display for Point2D {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "({:.6}, {:.6})", self.x, self.y)
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Point3D {
pub x: f64,
pub y: f64,
pub z: f64,
}
impl Point3D {
pub fn new(x: f64, y: f64, z: f64) -> Self {
Point3D { x, y, z }
}
pub fn xy(&self) -> Point2D {
Point2D::new(self.x, self.y)
}
}
impl From<(f64, f64, f64)> for Point3D {
fn from((x, y, z): (f64, f64, f64)) -> Self {
Point3D::new(x, y, z)
}
}
pub trait CoordTransform {
fn transform_fwd(&self, point: Point2D) -> Result<Point2D>;
fn transform_inv(&self, point: Point2D) -> Result<Point2D>;
fn transform_fwd_many(&self, points: &mut [Point2D]) -> Vec<Result<()>> {
points
.iter_mut()
.map(|p| {
let result = self.transform_fwd(*p)?;
*p = result;
Ok(())
})
.collect()
}
fn transform_inv_many(&self, points: &mut [Point2D]) -> Vec<Result<()>> {
points
.iter_mut()
.map(|p| {
let result = self.transform_inv(*p)?;
*p = result;
Ok(())
})
.collect()
}
}