oxiproj-transformations 0.1.2

Datum transformations and coordinate conversions for OxiProj.
Documentation
//! `set` conversion (set component values). Ported from PROJ src/conversions/set.cpp.
//!
//! Overwrites individual coordinate components with fixed values supplied via
//! the `+v_1 +v_2 +v_3 +v_4` parameters. Component `v_1` maps to slot 0, `v_2`
//! to slot 1, `v_3` to slot 2 and `v_4` to slot 3. Only components for which a
//! parameter is present are overwritten; the others pass through unchanged.
//!
//! In PROJ the forward (`set_fwd`) and inverse (`set_inv`) operations are
//! identical, so both directions apply the same overwrite here.

use crate::{TransBuild, TransParams};
use oxiproj_core::{Coord, IoUnits, Operation, ProjResult};

/// The `set` conversion. Ported from PROJ src/conversions/set.cpp.
#[derive(Debug)]
struct Set {
    v: [Option<f64>; 4],
}

impl Set {
    /// Apply the configured overwrites to a coordinate, leaving unset
    /// components untouched.
    fn apply(&self, c: Coord) -> Coord {
        let mut a = c.v();
        for (i, slot) in self.v.iter().enumerate() {
            if let Some(val) = *slot {
                a[i] = val;
            }
        }
        Coord::new(a[0], a[1], a[2], a[3])
    }
}

impl Operation for Set {
    /// Ported from PROJ src/conversions/set.cpp (`set_fwd`).
    fn forward_4d(&self, c: Coord) -> ProjResult<Coord> {
        Ok(self.apply(c))
    }

    /// Ported from PROJ src/conversions/set.cpp (`set_inv`); identical to the
    /// forward operation.
    fn inverse_4d(&self, c: Coord) -> ProjResult<Coord> {
        Ok(self.apply(c))
    }

    fn has_inverse(&self) -> bool {
        true
    }
}

/// Construct the `set` conversion. Ported from PROJ src/conversions/set.cpp
/// (`PJ_CONVERSION(set)`).
pub fn new(p: &TransParams) -> ProjResult<TransBuild> {
    let pa = p.params;
    let v = [
        pa.get_f64("v_1"),
        pa.get_f64("v_2"),
        pa.get_f64("v_3"),
        pa.get_f64("v_4"),
    ];
    Ok(TransBuild::new(
        Box::new(Set { v }),
        IoUnits::Whatever,
        IoUnits::Whatever,
    ))
}

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

    /// Test parameter lookup that only knows about `v_3 = 0.0`.
    struct SetV3;
    impl TransParamLookup for SetV3 {
        fn get_dms(&self, _key: &str) -> Option<f64> {
            None
        }
        fn get_f64(&self, key: &str) -> Option<f64> {
            if key == "v_3" {
                Some(0.0)
            } else {
                None
            }
        }
        fn get_int(&self, _key: &str) -> Option<i64> {
            None
        }
        fn get_str(&self, _key: &str) -> Option<&str> {
            None
        }
        fn get_bool(&self, _key: &str) -> bool {
            false
        }
        fn exists(&self, key: &str) -> bool {
            key == "v_3"
        }
    }

    #[test]
    fn set_v3_overwrites_slot2() {
        let ell = Ellipsoid::named("WGS84").unwrap();
        let b = new(&TransParams {
            ellipsoid: &ell,
            params: &SetV3,
            registry: None,
        })
        .unwrap();
        let op = b.operation;

        // cct: `12 55 100 5` with `+set +v_3=0` -> `12 55 0 5`.
        let fwd = op.forward_4d(Coord::new(12.0, 55.0, 100.0, 5.0)).unwrap();
        assert_eq!(fwd.v(), [12.0, 55.0, 0.0, 5.0]);

        // The inverse is identical to the forward operation.
        let inv = op.inverse_4d(Coord::new(12.0, 55.0, 100.0, 5.0)).unwrap();
        assert_eq!(inv.v(), [12.0, 55.0, 0.0, 5.0]);

        assert!(op.has_inverse());
    }
}