use geodesy::authoring::*;
use std::io::Write;
use std::mem::size_of;
use std::process::{Command, Stdio};
fn proj(args: &str, forward: bool, operands: &mut dyn CoordinateSet) -> usize {
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 Ok(mut child) = Command::new("proj")
.args(&proj_args)
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
else {
return 0;
};
let length = operands.len();
let buffer_size = 2 * length * size_of::<f64>();
let mut coo = Vec::with_capacity(buffer_size);
for i in 0..length {
let op = operands.get_coord(i);
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 {
warn!("proj: Unexpected return size");
return 0;
}
let mut errors = 0_usize;
for i in 0..length {
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;
}
let mut coord = operands.get_coord(i);
coord[0] = e;
coord[1] = n;
operands.set_coord(i, &coord);
}
operands.len() - errors
}
fn proj_fwd(op: &Op, _ctx: &dyn Context, operands: &mut dyn CoordinateSet) -> usize {
proj(&op.params.text["proj_args"], true, operands)
}
fn proj_inv(op: &Op, _ctx: &dyn Context, operands: &mut dyn CoordinateSet) -> usize {
proj(&op.params.text["proj_args"], false, operands)
}
#[rustfmt::skip]
pub const GAMUT: [OpParameter; 1] = [
OpParameter::Flag { key: "inv" },
];
pub fn proj_constructor(parameters: &RawParameters, _ctx: &dyn Context) -> Result<Op, Error> {
let def = ¶meters.instantiated_as;
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));
Ok(Op {
descriptor,
params,
steps: None,
})
}
fn main() -> anyhow::Result<()> {
let mut prv = geodesy::prelude::Minimal::new();
prv.register_op("proj", OpConstructor(proj_constructor));
let e = Ellipsoid::default();
if Command::new("proj").stderr(Stdio::piped()).spawn().is_err() {
println!("Cannot access the proj executable");
return Ok(());
}
let mut ctx = Minimal::default();
let op = ctx.op("proj proj=utm zone=32")?;
let mut geo = [Coor2D::geo(55., 12.)];
let utm = [Coor2D::raw(691875.63214, 6098907.82501)];
let rtp = [Coor2D::geo(55., 12.)];
println!("geo: {:?}", geo[0].to_degrees());
ctx.apply(op, Fwd, &mut geo)?;
assert!(geo[0].hypot2(&utm[0]) < 1e-5);
println!("projected: {:?}", geo[0]);
ctx.apply(op, Inv, &mut geo)?;
assert!(e.distance(&rtp[0], &geo[0]) < 1e-5);
println!("roundtrip: {:?}", geo[0].to_degrees());
let op = ctx.op("proj inv proj=utm zone=32")?;
let geo = [Coor2D::geo(55., 12.)];
let mut utm = [Coor2D::raw(691875.63214, 6098907.82501)];
let rtp = [Coor2D::raw(691875.63214, 6098907.82501)];
ctx.apply(op, Fwd, &mut utm)?;
assert!(e.distance(&utm[0], &geo[0]) < 1e-5);
ctx.apply(op, Inv, &mut utm)?;
assert!(rtp[0].hypot2(&utm[0]) < 1e-5);
Ok(())
}