use super::*;
const POST_DEFAULT: [f64; 4] = [0., 1., 2., 3.];
const MULT_DEFAULT: [f64; 4] = [1., 1., 1., 1.];
fn fwd(op: &Op, _ctx: &dyn Context, data: &mut [Coord]) -> Result<usize, Error> {
let n = data.len();
if op.params.boolean("noop") {
return Ok(n);
}
let post = op.params.series("post").unwrap_or(&POST_DEFAULT);
let post = [
post[0] as usize,
post[1] as usize,
post[2] as usize,
post[3] as usize,
];
let mult = op.params.series("mult").unwrap_or(&MULT_DEFAULT);
for o in data {
*o = Coord([
o[post[0]] * mult[0],
o[post[1]] * mult[1],
o[post[2]] * mult[2],
o[post[3]] * mult[3],
]);
}
Ok(n)
}
fn inv(op: &Op, _ctx: &dyn Context, data: &mut [Coord]) -> Result<usize, Error> {
let n = data.len();
if op.params.boolean("noop") {
return Ok(n);
}
let post = op.params.series("post").unwrap_or(&POST_DEFAULT);
let post = [
post[0] as usize,
post[1] as usize,
post[2] as usize,
post[3] as usize,
];
let mult = op.params.series("mult").unwrap_or(&MULT_DEFAULT);
let mult = [1. / mult[0], 1. / mult[1], 1. / mult[2], 1. / mult[3]];
for o in data {
let mut c = Coord::default();
for i in 0..4_usize {
c[post[i]] = o[i] * mult[post[i]];
}
*o = c;
}
Ok(n)
}
#[rustfmt::skip]
pub const GAMUT: [OpParameter; 3] = [
OpParameter::Flag { key: "inv" },
OpParameter::Text { key: "from", default: Some("enuf") },
OpParameter::Text { key: "to", default: Some("enuf") },
];
pub fn new(parameters: &RawParameters, _ctx: &dyn Context) -> Result<Op, Error> {
let mut params = ParsedParameters::new(parameters, &GAMUT)?;
let descriptor = OpDescriptor::new(¶meters.definition, InnerOp(fwd), Some(InnerOp(inv)));
let steps = Vec::<Op>::new();
let from = params.text("from")?;
let to = params.text("to")?;
let desc = coordinate_order_descriptor(&from);
if desc.is_none() {
return Err(Error::Operator("Adapt", "Bad value for 'from'"));
}
let from = desc.unwrap();
let desc = coordinate_order_descriptor(&to);
if desc.is_none() {
return Err(Error::Operator("Adapt", "Bad value for 'to'"));
}
let to = desc.unwrap();
let give = combine_descriptors(&from, &to);
if give.noop {
params.boolean.insert("noop");
}
let post = [
give.post[0] as f64,
give.post[1] as f64,
give.post[2] as f64,
give.post[3] as f64,
];
params.series.insert("post", Vec::from(post));
params.series.insert("mult", Vec::from(give.mult));
let id = OpHandle::new();
Ok(Op {
descriptor,
params,
steps,
id,
})
}
#[derive(Debug, Default, Clone)]
struct CoordinateOrderDescriptor {
post: [usize; 4],
mult: [f64; 4],
noop: bool,
}
#[allow(clippy::float_cmp)]
fn coordinate_order_descriptor(desc: &str) -> Option<CoordinateOrderDescriptor> {
let mut post = [0_usize, 1, 2, 3];
let mut mult = [1_f64, 1., 1., 1.];
if desc == "pass" {
return Some(CoordinateOrderDescriptor {
post,
mult,
noop: true,
});
}
if desc.len() != 4 && desc.len() != 8 {
return None;
}
let mut torad = 1_f64;
if desc.len() == 8 {
let good_angular = desc.ends_with("_deg")
|| desc.ends_with("_gon")
|| desc.ends_with("_rad")
|| desc.ends_with("_any");
if !good_angular {
return None;
}
if desc.ends_with("_deg") {
torad = std::f64::consts::PI / 180.;
} else if desc.ends_with("_gon") {
torad = std::f64::consts::PI / 200.;
}
}
let desc: Vec<char> = desc[0..4].chars().collect();
let mut indices = [1i32, 2, 3, 4];
for i in 0..4 {
let d = desc[i];
if !"neufswdp".contains(d) {
return None;
}
let dd: i32 = match d {
'w' => -1,
's' => -2,
'd' => -3,
'p' => -4,
'e' => 1,
'n' => 2,
'u' => 3,
'f' => 4,
_ => 0, };
indices[i] = dd;
}
let mut count = [0_usize, 0, 0, 0];
for i in 0..4 {
count[(indices[i].abs() - 1) as usize] += 1;
}
if count != [1, 1, 1, 1] {
warn!("adapt: {:?} is not a proper permutation", desc);
return None;
}
for i in 0..4 {
let d = indices[i];
post[i] = (d.abs() - 1) as usize;
mult[i] = d.signum() as f64 * if i > 1 { 1.0 } else { torad };
}
let noop = mult == [1.0; 4] && post == [0_usize, 1, 2, 3];
Some(CoordinateOrderDescriptor { post, mult, noop })
}
#[allow(clippy::float_cmp)]
fn combine_descriptors(
from: &CoordinateOrderDescriptor,
to: &CoordinateOrderDescriptor,
) -> CoordinateOrderDescriptor {
let mut give = CoordinateOrderDescriptor::default();
for i in 0..4 {
give.mult[i] = from.mult[i] / to.mult[i];
give.post[i] = from.post.iter().position(|&p| p == to.post[i]).unwrap();
}
give.noop = give.mult == [1.0; 4] && give.post == [0_usize, 1, 2, 3];
give
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn descriptor() {
use coordinate_order_descriptor as descriptor;
assert_eq!([1usize, 0, 2, 3], descriptor("neuf").unwrap().post);
assert_eq!([1usize, 0, 2, 3], descriptor("sedf_rad").unwrap().post);
assert_eq!([1usize, 0, 2, 3], descriptor("sedf_gon").unwrap().post);
assert_eq!([1usize, 0, 2, 3], descriptor("sedf_deg").unwrap().post);
assert_eq!([-1., 1., -1., 1.], descriptor("sedf_any").unwrap().mult);
assert_eq!(false, descriptor("sedf_any").unwrap().noop);
assert_eq!(true, descriptor("enuf_any").unwrap().noop);
assert_eq!(true, descriptor("enuf_rad").unwrap().noop);
assert_eq!(true, descriptor("enuf").unwrap().noop);
assert_eq!(true, descriptor("pass").unwrap().noop);
assert!(descriptor("sedf_pap").is_none());
assert!(descriptor("nsuf").is_none());
let from = descriptor("neuf_deg").unwrap();
let to = descriptor("wndf_gon").unwrap();
let give = combine_descriptors(&from, &to);
assert_eq!([1_usize, 0, 2, 3], give.post);
assert!(give.mult[0] + 400. / 360. < 1e-10); assert!(give.mult[1] - 400. / 360. < 1e-10); assert!(give.mult[2] + 1.0 < 1e-10); assert!(give.mult[3] - 1.0 < 1e-10); assert!(give.noop == false);
}
#[test]
fn adapt() -> Result<(), Error> {
let mut ctx = Minimal::default();
let gonify = ctx.op("adapt from = neuf_deg to = enuf_gon")?;
let mut data = [Coord::raw(90., 180., 0., 0.), Coord::raw(45., 90., 0., 0.)];
assert_eq!(ctx.apply(gonify, Fwd, &mut data)?, 2);
assert!((data[0][0] - 200.0).abs() < 1e-10);
assert!((data[0][1] - 100.0).abs() < 1e-10);
assert!((data[1][0] - 100.0).abs() < 1e-10);
assert!((data[1][1] - 50.0).abs() < 1e-10);
assert_eq!(data[1][2], 0.);
assert_eq!(data[1][3], 0.);
assert_eq!(ctx.apply(gonify, Inv, &mut data)?, 2);
assert!((data[0][0] - 90.0).abs() < 1e-10);
assert!((data[0][1] - 180.0).abs() < 1e-10);
assert!((data[1][0] - 45.0).abs() < 1e-10);
assert!((data[1][1] - 90.0).abs() < 1e-10);
Ok(())
}
#[test]
fn adapt_inv() -> Result<(), Error> {
let mut ctx = Minimal::default();
let degify = ctx.op("adapt inv from = neuf_deg to = enuf_gon")?;
let mut data = [
Coord::raw(200., 100., 0., 0.),
Coord::raw(100., 50., 0., 0.),
];
assert_eq!(ctx.apply(degify, Fwd, &mut data)?, 2);
assert!((data[0][0] - 90.0).abs() < 1e-10);
assert!((data[0][1] - 180.0).abs() < 1e-10);
assert!((data[1][0] - 45.0).abs() < 1e-10);
assert!((data[1][1] - 90.0).abs() < 1e-10);
assert_eq!(data[1][2], 0.);
assert_eq!(data[1][3], 0.);
assert_eq!(ctx.apply(degify, Inv, &mut data)?, 2);
assert!((data[0][0] - 200.0).abs() < 1e-10);
assert!((data[0][1] - 100.0).abs() < 1e-10);
assert!((data[1][0] - 100.0).abs() < 1e-10);
assert!((data[1][1] - 50.0).abs() < 1e-10);
Ok(())
}
#[test]
fn no_unit_conversion() -> Result<(), Error> {
let mut ctx = Minimal::default();
let mut data = some_basic_coordinates();
let swap = ctx.op("adapt from=neuf")?;
assert_eq!(ctx.apply(swap, Fwd, &mut data)?, 2);
assert_eq!(data[0][0], 12.0);
assert_eq!(data[0][1], 55.0);
Ok(())
}
#[test]
fn geo_gis_and_all_that() -> Result<(), Error> {
let mut ctx = Minimal::default();
ctx.register_resource("geo:in", "adapt from = neuf_deg");
ctx.register_resource("geo:out", "geo:in inv");
ctx.register_resource("gis:in", "adapt from = enuf_deg");
ctx.register_resource("gis:out", "gis:in inv");
let utm = ctx.op("geo:in | utm zone=32")?;
let geo = ctx.op("utm zone=32 inv | geo:out")?;
let mut data = some_basic_coordinates();
assert_eq!(ctx.apply(utm, Fwd, &mut data)?, 2);
assert!((data[0][0] - 691875.6321396603).abs() < 1e-9);
assert!((data[0][1] - 6098907.825005002).abs() < 1e-9);
assert_eq!(ctx.apply(geo, Fwd, &mut data)?, 2);
assert!((data[0][0] - 55.0).abs() < 1e-9);
assert!((data[0][1] - 12.0).abs() < 1e-9);
let mut data = some_basic_coordinates();
assert_eq!(ctx.apply(utm, Fwd, &mut data)?, 2);
assert!((data[0][0] - 691875.6321396603).abs() < 1e-9);
assert!((data[0][1] - 6098907.825005002).abs() < 1e-9);
assert_eq!(ctx.apply(utm, Inv, &mut data)?, 2);
assert!((data[0][0] - 55.0).abs() < 1e-9);
assert!((data[0][1] - 12.0).abs() < 1e-9);
let mut data = some_basic_coordinates();
let swap = ctx.op("geo:in | gis:out")?;
assert_eq!(ctx.apply(swap, Fwd, &mut data)?, 2);
assert!((data[0][0] - 12.0).abs() < 1e-9);
assert!((data[0][1] - 55.0).abs() < 1e-9);
Ok(())
}
}