use crate::authoring::*;
use std::{path::PathBuf, sync::Arc};
#[derive(Debug, Default)]
pub struct Minimal {
constructors: BTreeMap<String, OpConstructor>,
resources: BTreeMap<String, String>,
operators: BTreeMap<OpHandle, Op>,
}
const BAD_ID_MESSAGE: Error = Error::General("Minimal: Unknown operator id");
impl Context for Minimal {
fn new() -> Minimal {
let mut ctx = Minimal::default();
for item in BUILTIN_ADAPTORS {
ctx.register_resource(item.0, item.1);
}
ctx
}
fn op(&mut self, definition: &str) -> Result<OpHandle, Error> {
let op = Op::new(definition, self)?;
let id = OpHandle::new();
self.operators.insert(id, op);
assert!(self.operators.contains_key(&id));
Ok(id)
}
fn apply(
&self,
op: OpHandle,
direction: Direction,
operands: &mut dyn CoordinateSet,
) -> Result<usize, Error> {
let op = self.operators.get(&op).ok_or(BAD_ID_MESSAGE)?;
Ok(op.apply(self, operands, direction))
}
fn globals(&self) -> BTreeMap<String, String> {
BTreeMap::from([("ellps".to_string(), "GRS80".to_string())])
}
fn steps(&self, op: OpHandle) -> Result<Vec<String>, Error> {
let op = self.operators.get(&op).ok_or(BAD_ID_MESSAGE)?;
Ok(op.descriptor.instantiated_as.split_into_steps())
}
fn params(&self, op: OpHandle, index: usize) -> Result<ParsedParameters, Error> {
let op = self.operators.get(&op).ok_or(BAD_ID_MESSAGE)?;
if op.is_pipeline() {
let steps = op.steps.as_ref().unwrap();
if index >= steps.len() {
return Err(Error::General("Minimal: Bad step index"));
}
Ok(steps[index].params.clone())
} else {
if index > 0 {
return Err(Error::General("Minimal: Bad step index"));
}
Ok(op.params.clone())
}
}
fn register_op(&mut self, name: &str, constructor: OpConstructor) {
self.constructors.insert(String::from(name), constructor);
}
fn get_op(&self, name: &str) -> Result<OpConstructor, Error> {
if let Some(result) = self.constructors.get(name) {
return Ok(OpConstructor(result.0));
}
Err(Error::NotFound(
name.to_string(),
": User defined constructor".to_string(),
))
}
fn register_resource(&mut self, name: &str, definition: &str) {
self.resources
.insert(String::from(name), String::from(definition));
}
fn get_resource(&self, name: &str) -> Result<String, Error> {
if let Some(result) = self.resources.get(name) {
return Ok(result.to_string());
}
Err(Error::NotFound(
name.to_string(),
": User defined resource".to_string(),
))
}
fn get_blob(&self, name: &str) -> Result<Vec<u8>, Error> {
let n = PathBuf::from(name);
let ext = n
.extension()
.unwrap_or_default()
.to_str()
.unwrap_or_default();
let path: PathBuf = [".", "geodesy", ext, name].iter().collect();
Ok(std::fs::read(path)?)
}
fn get_grid(&self, _name: &str) -> Result<Arc<BaseGrid>, Error> {
Err(Error::General(
"Grid access by identifier not supported by the Minimal context provider",
))
}
}
#[cfg(test)]
mod tests {
use super::*;
use float_eq::assert_float_eq;
#[test]
fn basic() -> Result<(), Error> {
let mut ctx = Minimal::new();
ctx.register_resource("stupid:way", "addone | addone | addone inv");
let op = ctx.op("stupid:way")?;
let steps = ctx.steps(op)?;
assert_eq!(steps.len(), 3);
assert_eq!(steps[0], "addone");
assert_eq!(steps[1], "addone");
assert_eq!(steps[2], "addone inv");
let mut data = crate::test_data::coor2d();
assert_eq!(data[0].x(), 55.);
assert_eq!(data[1].x(), 59.);
assert_eq!(2, ctx.apply(op, Fwd, &mut data)?);
assert_eq!(data[0].x(), 56.);
assert_eq!(data[1].x(), 60.);
ctx.apply(op, Inv, &mut data)?;
assert_eq!(data[0].x(), 55.);
assert_eq!(data[1].x(), 59.);
let params = ctx.params(op, 1)?;
let ellps = params.ellps(0);
assert_eq!(ellps.semimajor_axis(), 6378137.);
Ok(())
}
#[test]
fn introspection() -> Result<(), Error> {
let mut ctx = Minimal::new();
let op = ctx.op("geo:in | utm zone=32 | neu:out")?;
let mut data = crate::test_data::coor2d();
assert_eq!(data[0].x(), 55.);
assert_eq!(data[1].x(), 59.);
ctx.apply(op, Fwd, &mut data)?;
let expected = [6098907.825005002, 691875.6321396609];
assert_float_eq!(data[0].0, expected, abs_all <= 1e-10);
let steps = ctx.steps(op)?;
assert_eq!(steps.len(), 3);
assert_eq!(steps[0], "geo:in");
assert_eq!(steps[1], "utm zone=32");
assert_eq!(steps[2], "neu:out");
assert_eq!("adapt", ctx.params(op, 0)?.name);
assert_eq!("adapt", ctx.params(op, 2)?.name);
assert_eq!("utm", ctx.params(op, 1)?.name);
let params = ctx.params(op, 1)?;
let ellps = params.ellps(0);
assert_eq!(ellps.semimajor_axis(), 6378137.);
assert_eq!(0., params.real("lat_0")?);
let zone = params.natural("zone")?;
assert_eq!(zone, 32);
Ok(())
}
#[test]
fn jacobian_test() -> Result<(), Error> {
let mut ctx = Minimal::new();
let expected = [
0.5743697198254788,
0.02465770672327687,
-0.04289429341613819,
0.9991676664740311,
];
let cph = Coor2D::geo(55., 12.);
let op = ctx.op("utm zone=32")?;
let steps = ctx.steps(op)?;
assert!(steps.len() == 1);
let ellps = ctx.params(op, 0)?.ellps(0);
let jac = Jacobian::new(
&ctx,
op,
[1f64.to_degrees(), 1.],
[false, false],
ellps,
cph,
)?;
assert_float_eq!(
[jac.dx_dlam, jac.dy_dlam, jac.dx_dphi, jac.dy_dphi],
expected,
abs_all <= 1e-12
);
let cph = Coor2D::raw(12., 55.);
let op = ctx.op("gis:in | utm zone=32")?;
let jac = Jacobian::new(&ctx, op, [1., 1.], [false, false], ellps, cph)?;
assert_float_eq!(
[jac.dx_dlam, jac.dy_dlam, jac.dx_dphi, jac.dy_dphi],
expected,
abs_all <= 1e-12
);
let cph = Coor2D::raw(55., 12.);
let op = ctx.op("geo:in | utm zone=32")?;
let jac = Jacobian::new(&ctx, op, [1., 1.], [true, false], ellps, cph)?;
assert_float_eq!(
[jac.dx_dlam, jac.dy_dlam, jac.dx_dphi, jac.dy_dphi],
expected,
abs_all <= 1e-12
);
let op = ctx.op("geo:in | utm zone=32 |neu:out")?;
let jac = Jacobian::new(&ctx, op, [1., 1.], [true, true], ellps, cph)?;
assert_float_eq!(
[jac.dx_dlam, jac.dy_dlam, jac.dx_dphi, jac.dy_dphi],
expected,
abs_all <= 1e-12
);
let op = ctx.op("geo:in | utm zone=32 |neu:out | helmert s=3e6")?;
let jac = Jacobian::new(&ctx, op, [1., 0.25], [true, true], ellps, cph)?;
assert_float_eq!(
[jac.dx_dlam, jac.dy_dlam, jac.dx_dphi, jac.dy_dphi],
expected,
abs_all <= 1e-12
);
Ok(())
}
}