use std::io::Write;
use std::mem::size_of;
use std::process::{Command, Stdio};
use super::*;
fn proj(args: &str, forward: bool, operands: &mut [Coord]) -> Result<usize, Error> {
let mut the_args = "-b ".to_string();
if !forward {
the_args += "-I ";
}
the_args += args;
let proj_args: Vec<&str> = the_args.split_whitespace().collect();
let mut child = Command::new("proj")
.args(&proj_args)
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()?;
let buffer_size = 2 * operands.len() * size_of::<f64>();
let mut coo = Vec::with_capacity(buffer_size);
for op in operands.iter() {
coo.extend_from_slice(&op[0].to_ne_bytes());
coo.extend_from_slice(&op[1].to_ne_bytes());
}
let mut stdin = child.stdin.take().expect("failed to get stdin");
std::thread::spawn(move || {
stdin.write_all(&coo).expect("failed to write to stdin");
});
let output = child.wait_with_output().expect("failed to wait on child");
if output.stdout.len() != buffer_size {
return Err(Error::General("proj: Unexpected return size"));
}
let mut errors = 0_usize;
for (i, op) in operands.iter_mut().enumerate() {
let start = 16 * i;
let ebytes: [u8; 8] = output.stdout[start..start + 8].try_into().unwrap_or([0; 8]);
let nbytes: [u8; 8] = output.stdout[start + 8..start + 16]
.try_into()
.unwrap_or([0; 8]);
let mut e = f64::from_ne_bytes(ebytes);
let mut n = f64::from_ne_bytes(nbytes);
if e == f64::INFINITY || n == f64::INFINITY {
e = f64::NAN;
n = f64::NAN;
errors += 1;
}
op[0] = e;
op[1] = n;
}
Ok(operands.len() - errors)
}
fn proj_fwd(op: &Op, _ctx: &dyn Context, operands: &mut [Coord]) -> Result<usize, Error> {
proj(&op.params.text["proj_args"], true, operands)
}
fn proj_inv(op: &Op, _ctx: &dyn Context, operands: &mut [Coord]) -> Result<usize, Error> {
proj(&op.params.text["proj_args"], false, operands)
}
#[rustfmt::skip]
pub const GAMUT: [OpParameter; 1] = [
OpParameter::Flag { key: "inv" },
];
pub fn new(parameters: &RawParameters, _ctx: &dyn Context) -> Result<Op, Error> {
let def = ¶meters.definition;
let given_args = ParsedParameters::new(parameters, &GAMUT)?.given;
if Command::new("proj").stderr(Stdio::piped()).spawn().is_err() {
return Err(Error::NotFound(
"proj".to_string(),
"Cannot locate the 'proj' executable".to_string(),
));
}
let mut proj_args = String::new();
for (k, v) in given_args {
if k == "inv" || k == "name" {
continue;
}
proj_args += " +";
proj_args += &k;
if v == "true" {
continue;
}
proj_args += "=";
proj_args += &v;
}
proj_args = proj_args.trim().to_string();
let mut params = ParsedParameters::new(parameters, &GAMUT)?;
params.text.insert("proj_args", proj_args);
let fwd = InnerOp(proj_fwd);
let inv = InnerOp(proj_inv);
let descriptor = OpDescriptor::new(def, fwd, Some(inv));
let steps = Vec::<Op>::new();
let id = OpHandle::new();
Ok(Op {
descriptor,
params,
steps,
id,
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn proj() -> Result<(), Error> {
if Command::new("proj").stderr(Stdio::piped()).spawn().is_err() {
return Ok(());
}
let mut ctx = Minimal::default();
let op = ctx.op("proj proj=utm zone=32")?;
let mut geo = [Coord::geo(55., 12., 0., 0.)];
let utm = [Coord::raw(691875.63214, 6098907.82501, 0., 0.)];
let rtp = [Coord::geo(55., 12., 0., 0.)];
ctx.apply(op, Fwd, &mut geo)?;
assert!(geo[0].hypot2(&utm[0]) < 1e-5);
ctx.apply(op, Inv, &mut geo)?;
assert!(rtp[0].default_ellps_dist(&geo[0]) < 1e-5);
let op = ctx.op("proj inv proj=utm zone=32")?;
let geo = [Coord::geo(55., 12., 0., 0.)];
let mut utm = [Coord::raw(691875.63214, 6098907.82501, 0., 0.)];
let rtp = [Coord::raw(691875.63214, 6098907.82501, 0., 0.)];
ctx.apply(op, Fwd, &mut utm)?;
assert!(geo[0].default_ellps_dist(&utm[0]) < 1e-5);
ctx.apply(op, Inv, &mut utm)?;
assert!(rtp[0].hypot2(&utm[0]) < 1e-5);
Ok(())
}
}