oxiproj-engine 0.1.0

Proj-string parser, operation dispatch, and transformation pipelines for OxiProj.
Documentation
//! Multi-step transformation pipeline.
//!
//! Ported from PROJ 9.8.0 `src/pipeline.cpp` (`pipeline_forward_4d` /
//! `pipeline_reverse_4d`).

use oxiproj_core::{Coord, Lp, Operation, ProjResult, Xy};

/// A sequence of [`crate::pj::Pj`] steps applied in order (forward) or in
/// reverse (inverse). The 4D methods are the true entry points; the 2D methods
/// wrap through them.
#[derive(Debug)]
pub struct Pipeline {
    /// The ordered steps. Each step honors its own `inverted` flag.
    pub steps: Vec<crate::pj::Pj>,
}

impl Operation for Pipeline {
    fn forward_4d(&self, c: Coord) -> ProjResult<Coord> {
        let mut point = c;
        for step in &self.steps {
            if point.is_error() {
                return Ok(Coord::error());
            }
            point = step.forward_4d(point)?;
        }
        Ok(point)
    }

    fn inverse_4d(&self, c: Coord) -> ProjResult<Coord> {
        let mut point = c;
        for step in self.steps.iter().rev() {
            if point.is_error() {
                return Ok(Coord::error());
            }
            point = step.inverse_4d(point)?;
        }
        Ok(point)
    }

    fn forward_2d(&self, lp: Lp) -> ProjResult<Xy> {
        let c = self.forward_4d(Coord::new(lp.lam, lp.phi, 0.0, 0.0))?;
        Ok(c.xy())
    }

    fn inverse_2d(&self, xy: Xy) -> ProjResult<Lp> {
        let c = self.inverse_4d(Coord::new(xy.x, xy.y, 0.0, 0.0))?;
        Ok(c.lp())
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use oxiproj_core::DEG_TO_RAD;

    #[test]
    fn utm_forward_then_inverse_round_trips() {
        // Two UTM zone-32 steps: the second inverted, so the pair is an
        // identity in geographic space.
        let step0 = crate::create::create("+proj=utm +zone=32 +ellps=WGS84").unwrap();
        let mut step1 = crate::create::create("+proj=utm +zone=32 +ellps=WGS84").unwrap();
        step1.inverted = true;
        let pipe = Pipeline {
            steps: vec![step0, step1],
        };
        let input = Coord::new(12.0 * DEG_TO_RAD, 55.0 * DEG_TO_RAD, 0.0, 0.0);
        let out = pipe.forward_4d(input).unwrap();
        let o = out.v();
        assert!((o[0] - 12.0 * DEG_TO_RAD).abs() < 1e-9, "lam got {}", o[0]);
        assert!((o[1] - 55.0 * DEG_TO_RAD).abs() < 1e-9, "phi got {}", o[1]);
    }
}