use std::cell::RefCell;
use crate::{TransBuild, TransParams};
use oxiproj_core::{Coord, IoUnits, Operation, ProjError, ProjResult};
thread_local! {
static COORD_STACKS: RefCell<[Vec<f64>; 4]> =
const { RefCell::new([Vec::new(), Vec::new(), Vec::new(), Vec::new()]) };
}
fn do_push(c: Coord, which: [bool; 4]) -> ProjResult<Coord> {
let v = c.v();
COORD_STACKS.with(|cell| {
let mut stacks = cell.borrow_mut();
for i in 0..4 {
if which[i] {
stacks[i].push(v[i]);
}
}
});
Ok(c)
}
fn do_pop(c: Coord, which: [bool; 4]) -> ProjResult<Coord> {
let mut v = c.v();
COORD_STACKS.with(|cell| {
let mut stacks = cell.borrow_mut();
for i in 0..4 {
if which[i] {
if let Some(val) = stacks[i].pop() {
v[i] = val;
}
}
}
});
Ok(Coord::new(v[0], v[1], v[2], v[3]))
}
#[derive(Debug)]
struct PushOp {
which: [bool; 4],
}
#[derive(Debug)]
struct PopOp {
which: [bool; 4],
}
impl Operation for PushOp {
fn forward_4d(&self, c: Coord) -> ProjResult<Coord> {
do_push(c, self.which)
}
fn inverse_4d(&self, c: Coord) -> ProjResult<Coord> {
do_pop(c, self.which)
}
fn has_inverse(&self) -> bool {
true
}
}
impl Operation for PopOp {
fn forward_4d(&self, c: Coord) -> ProjResult<Coord> {
do_pop(c, self.which)
}
fn inverse_4d(&self, c: Coord) -> ProjResult<Coord> {
do_push(c, self.which)
}
fn has_inverse(&self) -> bool {
true
}
}
fn parse_which(p: &TransParams) -> [bool; 4] {
let v1 = p.params.exists("v_1");
let v2 = p.params.exists("v_2");
let v3 = p.params.exists("v_3");
let v4 = p.params.exists("v_4");
if !v1 && !v2 && !v3 && !v4 {
[true, true, true, true]
} else {
[v1, v2, v3, v4]
}
}
pub fn new_push(p: &TransParams) -> ProjResult<TransBuild> {
let _ = ProjError::InvalidOp; Ok(TransBuild::new(
Box::new(PushOp {
which: parse_which(p),
}),
IoUnits::Whatever,
IoUnits::Whatever,
))
}
pub fn new_pop(p: &TransParams) -> ProjResult<TransBuild> {
Ok(TransBuild::new(
Box::new(PopOp {
which: parse_which(p),
}),
IoUnits::Whatever,
IoUnits::Whatever,
))
}
#[cfg(test)]
mod tests {
use super::*;
use oxiproj_core::{Coord, Ellipsoid};
struct NoParams;
impl crate::TransParamLookup for NoParams {
fn get_dms(&self, _key: &str) -> Option<f64> {
None
}
fn get_f64(&self, _key: &str) -> Option<f64> {
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 {
false
}
}
struct V1V2Params;
impl crate::TransParamLookup for V1V2Params {
fn get_dms(&self, _key: &str) -> Option<f64> {
None
}
fn get_f64(&self, _key: &str) -> Option<f64> {
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_1" || key == "v_2"
}
}
fn wgs84() -> Ellipsoid {
Ellipsoid::named("WGS84").expect("WGS84 ellipsoid must be available")
}
fn clear_stacks() {
COORD_STACKS.with(|cell| {
let mut stacks = cell.borrow_mut();
for stack in stacks.iter_mut() {
stack.clear();
}
});
}
#[test]
fn push_all_then_pop_all_round_trips() -> ProjResult<()> {
clear_stacks();
let push = PushOp {
which: [true, true, true, true],
};
let pop = PopOp {
which: [true, true, true, true],
};
let original = Coord::new(1.0, 2.0, 3.0, 4.0);
let after_push = push.forward_4d(original)?;
assert_eq!(after_push.v(), [1.0, 2.0, 3.0, 4.0]);
let modified = Coord::new(9.0, 8.0, 7.0, 6.0);
let restored = pop.forward_4d(modified)?;
assert_eq!(restored.v(), [1.0, 2.0, 3.0, 4.0]);
Ok(())
}
#[test]
fn push_v1_v2_only_preserves_v3_v4() -> ProjResult<()> {
clear_stacks();
let push = PushOp {
which: [true, true, false, false],
};
let pop = PopOp {
which: [true, true, false, false],
};
let original = Coord::new(10.0, 20.0, 30.0, 40.0);
push.forward_4d(original)?;
let modified = Coord::new(99.0, 88.0, 77.0, 66.0);
let restored = pop.forward_4d(modified)?;
assert_eq!(restored.v()[0], 10.0);
assert_eq!(restored.v()[1], 20.0);
assert_eq!(restored.v()[2], 77.0);
assert_eq!(restored.v()[3], 66.0);
Ok(())
}
#[test]
fn push_inverse_acts_as_pop() -> ProjResult<()> {
clear_stacks();
let push = PushOp {
which: [true, true, true, true],
};
let saved = Coord::new(5.0, 6.0, 7.0, 8.0);
push.forward_4d(saved)?;
let modified = Coord::new(0.0, 0.0, 0.0, 0.0);
let restored = push.inverse_4d(modified)?;
assert_eq!(restored.v(), [5.0, 6.0, 7.0, 8.0]);
Ok(())
}
#[test]
fn pop_inverse_acts_as_push() -> ProjResult<()> {
clear_stacks();
let pop = PopOp {
which: [true, true, true, true],
};
let saved = Coord::new(11.0, 22.0, 33.0, 44.0);
pop.inverse_4d(saved)?;
let modified = Coord::new(0.0, 0.0, 0.0, 0.0);
let restored = pop.forward_4d(modified)?;
assert_eq!(restored.v(), [11.0, 22.0, 33.0, 44.0]);
Ok(())
}
#[test]
fn no_params_defaults_to_all_components() {
let which = parse_which(&TransParams {
ellipsoid: &wgs84(),
params: &NoParams,
registry: None,
});
assert_eq!(which, [true, true, true, true]);
}
#[test]
fn v1_v2_params_selects_first_two() {
let which = parse_which(&TransParams {
ellipsoid: &wgs84(),
params: &V1V2Params,
registry: None,
});
assert_eq!(which, [true, true, false, false]);
}
#[test]
fn new_push_builds_successfully() {
let ell = wgs84();
let p = TransParams {
ellipsoid: &ell,
params: &NoParams,
registry: None,
};
assert!(new_push(&p).is_ok());
}
#[test]
fn new_pop_builds_successfully() {
let ell = wgs84();
let p = TransParams {
ellipsoid: &ell,
params: &NoParams,
registry: None,
};
assert!(new_pop(&p).is_ok());
}
#[test]
fn stack_underflow_leaves_component_unchanged() -> ProjResult<()> {
clear_stacks();
let pop = PopOp {
which: [true, false, false, false],
};
let c = Coord::new(99.0, 1.0, 2.0, 3.0);
let result = pop.forward_4d(c)?;
assert_eq!(result.v()[0], 99.0); Ok(())
}
#[test]
fn push_and_pop_have_inverse() {
let push = PushOp { which: [true; 4] };
let pop = PopOp { which: [true; 4] };
assert!(push.has_inverse());
assert!(pop.has_inverse());
}
}