use crate::pj::Pj;
pub(crate) fn optimize_pipeline(steps: Vec<Pj>) -> Vec<Pj> {
let steps = remove_complete_noops(steps);
cancel_axisswap_pairs(steps)
}
fn remove_complete_noops(steps: Vec<Pj>) -> Vec<Pj> {
steps
.into_iter()
.filter(|s| !(s.omit_fwd && s.omit_inv))
.collect()
}
fn cancel_axisswap_pairs(steps: Vec<Pj>) -> Vec<Pj> {
let mut result: Vec<Pj> = Vec::with_capacity(steps.len());
let mut iter = steps.into_iter().peekable();
while let Some(current) = iter.next() {
let cancels = iter
.peek()
.map(|next| {
current.op_name == "axisswap"
&& next.op_name == "axisswap"
&& axisswap_steps_cancel(¤t, next)
})
.unwrap_or(false);
if cancels {
let _ = iter.next();
} else {
result.push(current);
}
}
result
}
fn axisswap_steps_cancel(a: &Pj, b: &Pj) -> bool {
if a.omit_fwd || a.omit_inv || b.omit_fwd || b.omit_inv {
return false;
}
a.lam0 == b.lam0
&& a.phi0 == b.phi0
&& a.x0 == b.x0
&& a.y0 == b.y0
&& a.k0 == b.k0
&& a.ellipsoid.a == b.ellipsoid.a
&& a.ellipsoid.es == b.ellipsoid.es
}
#[cfg(test)]
mod tests {
use super::*;
use crate::pj::Pj;
use oxiproj_core::{Ellipsoid, IoUnits};
#[derive(Debug)]
struct IdOp;
impl oxiproj_core::Operation for IdOp {
fn forward_2d(&self, lp: oxiproj_core::Lp) -> oxiproj_core::ProjResult<oxiproj_core::Xy> {
Ok(oxiproj_core::Xy::new(lp.lam, lp.phi))
}
fn inverse_2d(&self, xy: oxiproj_core::Xy) -> oxiproj_core::ProjResult<oxiproj_core::Lp> {
Ok(oxiproj_core::Lp::new(xy.x, xy.y))
}
}
fn make_pj(op_name: &str, omit_fwd: bool, omit_inv: bool) -> Pj {
Pj {
operation: Box::new(IdOp),
ellipsoid: Ellipsoid::named("WGS84").expect("WGS84"),
lam0: 0.0,
phi0: 0.0,
x0: 0.0,
y0: 0.0,
z0: 0.0,
k0: 1.0,
to_meter: 1.0,
fr_meter: 1.0,
vto_meter: 1.0,
vfr_meter: 1.0,
from_greenwich: 0.0,
over: false,
geoc: false,
is_latlong: false,
left: IoUnits::Whatever,
right: IoUnits::Whatever,
inverted: false,
bypass_prepare_finalize: true,
omit_fwd,
omit_inv,
ad_proj: None,
op_name: op_name.to_string(),
}
}
#[test]
fn noop_removal_both_omit() {
let steps = vec![
make_pj("noop", false, false),
make_pj("noop", true, true), make_pj("noop", false, false),
];
let result = optimize_pipeline(steps);
assert_eq!(
result.len(),
2,
"middle step with omit_fwd+omit_inv must be removed"
);
}
#[test]
fn noop_removal_only_fwd_kept() {
let steps = vec![make_pj("noop", true, false)];
let result = optimize_pipeline(steps);
assert_eq!(result.len(), 1, "omit_fwd-only step must be kept");
}
#[test]
fn noop_removal_only_inv_kept() {
let steps = vec![make_pj("noop", false, true)];
let result = optimize_pipeline(steps);
assert_eq!(result.len(), 1, "omit_inv-only step must be kept");
}
#[test]
fn axisswap_pair_cancelled() {
let steps = vec![
make_pj("axisswap", false, false),
make_pj("axisswap", false, false),
];
let result = optimize_pipeline(steps);
assert_eq!(result.len(), 0, "two identical axisswap steps must cancel");
}
#[test]
fn axisswap_with_omit_not_cancelled() {
let steps = vec![
make_pj("axisswap", true, false), make_pj("axisswap", false, false),
];
let result = optimize_pipeline(steps);
assert_eq!(
result.len(),
2,
"axisswap with selective omit must not cancel"
);
}
#[test]
fn axisswap_singleton_kept() {
let steps = vec![make_pj("axisswap", false, false)];
let result = optimize_pipeline(steps);
assert_eq!(result.len(), 1, "single axisswap must be kept");
}
#[test]
fn empty_input_stays_empty() {
let result = optimize_pipeline(vec![]);
assert!(result.is_empty());
}
}