use crate::authoring::*;
fn fwd(op: &Op, ctx: &dyn Context, operands: &mut dyn CoordinateSet) -> usize {
let grids = &op.params.grids;
let use_null_grid = op.params.boolean("null_grid");
let mut successes = 0_usize;
let n = operands.len();
if grids.is_empty() {
return n;
}
let ctx = Some(ctx);
for i in 0..n {
let mut coord = operands.get_coord(i);
if let Some(d) = grids_at(ctx, grids, coord, use_null_grid) {
if grids[0].bands() == 1 {
coord[2] -= d[0];
operands.set_coord(i, &coord);
successes += 1;
continue;
}
let d = d.arcsec_to_radians();
coord[0] += d[0];
coord[1] += d[1];
operands.set_coord(i, &coord);
successes += 1;
continue;
}
operands.set_coord(i, &Coor4D::nan());
}
successes
}
fn inv(op: &Op, ctx: &dyn Context, operands: &mut dyn CoordinateSet) -> usize {
let grids = &op.params.grids;
let use_null_grid = op.params.boolean("null_grid");
let mut successes = 0_usize;
let n = operands.len();
if grids.is_empty() {
return n;
}
let ctx = Some(ctx);
'points: for i in 0..n {
let mut coord = operands.get_coord(i);
if let Some(t) = grids_at(ctx, grids, coord, use_null_grid) {
if grids[0].bands() == 1 {
coord[2] += t[0];
operands.set_coord(i, &coord);
successes += 1;
continue;
}
let t = t.arcsec_to_radians();
let mut t = coord - t;
for _ in 0..10 {
if let Some(t2) = grids_at(ctx, grids, t, use_null_grid) {
let d = t - coord + t2.arcsec_to_radians();
t = t - d;
if d[0].hypot(d[1]) < 1e-12 {
operands.set_coord(i, &t);
successes += 1;
continue 'points;
}
continue;
}
operands.set_coord(i, &Coor4D::nan());
continue 'points;
}
}
}
successes
}
#[rustfmt::skip]
pub const GAMUT: [OpParameter; 3] = [
OpParameter::Flag { key: "inv" },
OpParameter::Texts { key: "grids", default: None },
OpParameter::Real { key: "padding", default: Some(0.5) },
];
pub fn new(parameters: &RawParameters, ctx: &dyn Context) -> Result<Op, Error> {
let def = ¶meters.instantiated_as;
let mut params = ParsedParameters::new(parameters, &GAMUT)?;
for mut grid_name in params.texts("grids")?.clone() {
let optional = grid_name.starts_with('@');
if optional {
grid_name = grid_name.trim_start_matches('@').to_string();
}
if grid_name == "null" {
params.boolean.insert("null_grid");
break; }
match ctx.get_grid(&grid_name) {
Ok(grid) => params.grids.push(grid),
Err(e) => {
if !optional {
return Err(e);
}
}
}
}
let fwd = InnerOp(fwd);
let inv = InnerOp(inv);
let descriptor = OpDescriptor::new(def, fwd, Some(inv));
Ok(Op {
descriptor,
params,
steps: None,
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::coordinate::AngularUnits;
#[test]
fn gridshift() -> Result<(), Error> {
let mut ctx = Plain::default();
let op = ctx.op("gridshift grids=test.datum")?;
let cph = Coor4D::geo(55., 12., 0., 0.);
let mut data = [cph];
ctx.apply(op, Fwd, &mut data)?;
let res = data[0].to_geo();
assert!((res[0] - 55.015278).abs() < 1e-6);
assert!((res[1] - 12.003333).abs() < 1e-6);
ctx.apply(op, Inv, &mut data)?;
assert!((data[0][0] - cph[0]).abs() < 1e-10);
assert!((data[0][1] - cph[1]).abs() < 1e-10);
Ok(())
}
#[test]
fn ntv2() -> Result<(), Error> {
let mut ctx = Plain::default();
let op = ctx.op("gridshift grids=100800401.gsb")?;
let bcn = Coor2D::geo(41.3874, 2.1686);
let mut data = [bcn];
ctx.apply(op, Fwd, &mut data)?;
let res = data[0].to_geo();
assert!((res[0] - 41.38627500250805).abs() < 1e-8);
assert!((res[1] - 2.167450821894838).abs() < 1e-8);
ctx.apply(op, Inv, &mut data)?;
assert!((data[0][0] - bcn[0]).abs() < 1e-10);
assert!((data[0][1] - bcn[1]).abs() < 1e-10);
Ok(())
}
#[test]
fn multiple_grids() -> Result<(), Error> {
let mut ctx = Plain::default();
let op = ctx.op("gridshift grids=test.datum, test.datum")?;
let cph = Coor4D::geo(55., 12., 0., 0.);
let mut data = [cph];
ctx.apply(op, Fwd, &mut data)?;
let res = data[0].to_geo();
assert!((res[0] - 55.015278).abs() < 1e-6);
assert!((res[1] - 12.003333).abs() < 1e-6);
Plain::clear_grids();
Plain::clear_grids();
ctx.apply(op, Inv, &mut data)?;
assert!((data[0][0] - cph[0]).abs() < 1e-10);
assert!((data[0][1] - cph[1]).abs() < 1e-10);
Ok(())
}
#[test]
fn fails_without_null_grid() -> Result<(), Error> {
let mut ctx = Plain::default();
let op = ctx.op("gridshift grids=test.datum")?;
let ldn = Coor4D::geo(51.505, -0.09, 0., 0.);
let mut data = [ldn];
let successes = ctx.apply(op, Fwd, &mut data)?;
assert_eq!(successes, 0);
assert!(data[0][0].is_nan());
assert!(data[0][1].is_nan());
Ok(())
}
#[test]
fn passes_with_null_grid() -> Result<(), Error> {
let mut ctx = Plain::default();
let op = ctx.op("gridshift grids=test.datum, @null")?;
let ldn = Coor4D::geo(51.505, -0.09, 0., 0.);
let mut data = [ldn];
let successes = ctx.apply(op, Fwd, &mut data)?;
let res = data[0].to_geo();
assert_eq!(successes, 1);
assert_eq!(res[0], 51.505);
assert_eq!(res[1], -0.09);
let successes = ctx.apply(op, Inv, &mut data)?;
assert_eq!(successes, 1);
assert!((data[0][0] - ldn[0]).abs() < 1e-10);
assert!((data[0][1] - ldn[1]).abs() < 1e-10);
Ok(())
}
#[test]
fn optional_grid() -> Result<(), Error> {
let mut ctx = Plain::default();
let op = ctx.op("gridshift grids=@test_subset.datum, @missing.gsb, test.datum")?;
let mut ctx2 = Plain::default();
let op2 = ctx2.op("gridshift grids=@test_subset.datum, @missing.gsb, test.datum")?;
let cph = Coor2D::geo(55., 12.);
let mut data = [cph];
let mut data2 = [cph];
ctx.apply(op, Fwd, &mut data)?;
let res = data[0].to_geo();
assert!((res[0] - 55.015278).abs() < 1e-6);
assert!((res[1] - 12.003333).abs() < 1e-6);
ctx2.apply(op2, Fwd, &mut data2)?;
assert_eq!(data, data2);
ctx.apply(op, Inv, &mut data)?;
assert!((data[0][0] - cph[0]).abs() < 1e-10);
assert!((data[0][1] - cph[1]).abs() < 1e-10);
ctx2.apply(op2, Inv, &mut data2)?;
assert_eq!(data, data2);
let haby = Coor4D::geo(55.97, 11.33, 0., 0.);
let mut data = [haby];
let expected_correction = Coor4D([11.331, 55.971, 0., 0.]);
ctx.apply(op, Fwd, &mut data)?;
let correction = ((data[0] - haby) * Coor4D([3600., 3600., 3600., 3600.])).to_degrees();
assert!((correction - expected_correction)[0].abs() < 1e-6);
assert!((correction - expected_correction)[1].abs() < 1e-6);
Ok(())
}
#[test]
fn missing_grid() -> Result<(), Error> {
let mut ctx = Plain::default();
let op = ctx.op("gridshift grids=missing.gsb");
assert!(op.is_err());
Ok(())
}
}