Skip to main content

oxiproj_transformations/
lib.rs

1#![forbid(unsafe_code)]
2//! `oxiproj-transformations` — datum transformations and coordinate conversions
3//! ported from PROJ 9.8.0 `src/conversions/` and `src/transformations/`.
4//!
5//! Each operation implements [`oxiproj_core::Operation`] as a 4D transform,
6//! mirroring the `oxiproj-projections` crate's build/param pattern. This crate
7//! depends on `oxiproj-core` and `oxiproj-grids`.
8
9pub mod batch_helmert;
10pub mod conversions;
11mod internal;
12pub mod transformations;
13
14use oxiproj_core::{Ellipsoid, IoUnits, Operation, ProjError, ProjResult};
15
16// Re-export from `oxiproj-core` so that `oxiproj-grids` can implement it
17// without a circular dependency. All existing users of
18// `oxiproj_transformations::GridRegistry` continue to work unchanged.
19pub use oxiproj_core::GridRegistry;
20
21pub use transformations::helmert_batch;
22
23// ---- Epoch-aware coordinate propagation (T3.1 foundation / v0.1.1) ----
24pub mod epoch_transform;
25pub use epoch_transform::{epoch_path, plate_for_frame, propagate_epoch};
26
27// ---- Covariance/uncertainty propagation through transforms (T7.5 / v0.1.1) ----
28pub mod covariance;
29pub use covariance::{
30    propagate_through_affine_2d, propagate_through_helmert_3, propagate_through_helmert_7_xy,
31};
32
33pub mod frame_chain;
34pub use frame_chain::{
35    apply_frame_transform, find_frame_transform, FrameTransform, FRAME_TRANSFORMS,
36};
37
38// ---- Epoch-aware coordinate transformation pipeline (T3.1 / v0.1.1) ----
39pub mod epoch_pipeline;
40pub use epoch_pipeline::{transform_epoch_aware, EpochAwareResult};
41
42/// A trait the engine implements to give a transformation access to its specific params.
43/// Mirrors `oxiproj_projections::ProjParamLookup`.
44pub trait TransParamLookup {
45    /// Look up a key whose value is an angle, returning radians (parsed via PROJ `dmstor`).
46    fn get_dms(&self, key: &str) -> Option<f64>;
47    /// Look up a key whose value is a plain floating-point number.
48    fn get_f64(&self, key: &str) -> Option<f64>;
49    /// Look up a key whose value is an integer.
50    fn get_int(&self, key: &str) -> Option<i64>;
51    /// Look up a key whose value is a string.
52    fn get_str(&self, key: &str) -> Option<&str>;
53    /// Return whether a boolean flag is set (bare `+key` or `+key=t/T/true`).
54    fn get_bool(&self, key: &str) -> bool;
55    /// Return whether the key is present at all.
56    fn exists(&self, key: &str) -> bool;
57}
58
59/// Parameters the engine has parsed and hands to a transformation constructor.
60pub struct TransParams<'a> {
61    /// The ellipsoid the transformation operates on.
62    pub ellipsoid: &'a Ellipsoid,
63    /// Access to the transformation-specific parameters.
64    pub params: &'a dyn TransParamLookup,
65    /// Optional grid registry for grid-based transforms.
66    pub registry: Option<&'a dyn GridRegistry>,
67}
68
69/// The constructor result: a boxed operation plus engine-facing unit metadata.
70pub struct TransBuild {
71    /// The boxed operation.
72    pub operation: Box<dyn Operation>,
73    /// Input units of the operation (left side of the pipeline).
74    pub left: IoUnits,
75    /// Output units of the operation (right side of the pipeline).
76    pub right: IoUnits,
77}
78
79impl TransBuild {
80    /// Build a `TransBuild` from an operation and its left/right units.
81    pub fn new(operation: Box<dyn Operation>, left: IoUnits, right: IoUnits) -> TransBuild {
82        TransBuild {
83            operation,
84            left,
85            right,
86        }
87    }
88}
89
90/// Build a transformation/conversion by its PROJ name. Mirrors the projections
91/// crate's `build`, chaining the per-group `build` functions.
92pub fn build(name: &str, p: &TransParams) -> ProjResult<TransBuild> {
93    conversions::build(name, p)
94        .or_else(|| transformations::build(name, p))
95        .unwrap_or(Err(ProjError::InvalidOp))
96}
97
98#[cfg(test)]
99mod tests {
100    use super::*;
101    use oxiproj_core::Ellipsoid;
102
103    struct NoParams;
104    impl TransParamLookup for NoParams {
105        fn get_dms(&self, _key: &str) -> Option<f64> {
106            None
107        }
108        fn get_f64(&self, _key: &str) -> Option<f64> {
109            None
110        }
111        fn get_int(&self, _key: &str) -> Option<i64> {
112            None
113        }
114        fn get_str(&self, _key: &str) -> Option<&str> {
115            None
116        }
117        fn get_bool(&self, _key: &str) -> bool {
118            false
119        }
120        fn exists(&self, _key: &str) -> bool {
121            false
122        }
123    }
124
125    fn wgs84() -> Ellipsoid {
126        Ellipsoid::named("WGS84").unwrap()
127    }
128
129    #[test]
130    fn build_noop_succeeds() {
131        let ell = wgs84();
132        let np = NoParams;
133        let pp = TransParams {
134            ellipsoid: &ell,
135            params: &np,
136            registry: None,
137        };
138        assert!(build("noop", &pp).is_ok());
139    }
140
141    #[test]
142    fn build_unknown_is_invalid_op() {
143        let ell = wgs84();
144        let np = NoParams;
145        let pp = TransParams {
146            ellipsoid: &ell,
147            params: &np,
148            registry: None,
149        };
150        assert_eq!(build("nonexistent", &pp).err(), Some(ProjError::InvalidOp));
151    }
152}