#![forbid(unsafe_code)]
use crate::{TransBuild, TransParams};
use oxiproj_core::{Coord, IoUnits, Operation, ProjError, ProjResult};
use oxiproj_grids::{read_tinshift, TinShiftIndex};
#[derive(Debug)]
struct TinShiftTransform {
index: TinShiftIndex,
}
impl Operation for TinShiftTransform {
fn forward_4d(&self, c: Coord) -> ProjResult<Coord> {
let v = c.v();
let lon_deg = v[0].to_degrees();
let lat_deg = v[1].to_degrees();
let (dx_deg, dy_deg) = self
.index
.interpolate(lon_deg, lat_deg)
.ok_or(ProjError::OutsideGrid)?;
Ok(Coord::new(
v[0] + dx_deg.to_radians(),
v[1] + dy_deg.to_radians(),
v[2],
v[3],
))
}
fn inverse_4d(&self, _c: Coord) -> ProjResult<Coord> {
Err(ProjError::NoInverseOp)
}
fn has_inverse(&self) -> bool {
false
}
}
pub fn new(p: &TransParams) -> ProjResult<TransBuild> {
let grid_name = p.params.get_str("grids").ok_or(ProjError::MissingArg)?;
let registry = p.registry.ok_or(ProjError::FileNotFound)?;
let data = registry
.get_grid(grid_name)
.ok_or(ProjError::FileNotFound)?;
let index = read_tinshift(data)?;
Ok(TransBuild::new(
Box::new(TinShiftTransform { index }),
IoUnits::Radians,
IoUnits::Radians,
))
}
#[cfg(test)]
mod tests {
use super::*;
use oxiproj_core::DEG_TO_RAD;
fn make_tinshift_json() -> Vec<u8> {
let s = r#"{
"file_type": "tinshift_v1",
"input_crs": "EPSG:4326",
"output_crs": "EPSG:4167",
"triangulation": {
"vertices": [[0.0, 0.0], [1.0, 0.0], [0.5, 1.0], [0.0, 1.0]],
"triangles": [[0, 1, 2], [0, 2, 3]],
"offset_x": [0.1, 0.2, 0.3, 0.0],
"offset_y": [0.4, 0.5, 0.6, 0.0]
}
}"#;
s.as_bytes().to_vec()
}
struct InMemReg(std::collections::HashMap<String, Vec<u8>>);
impl crate::GridRegistry for InMemReg {
fn get_grid(&self, name: &str) -> Option<&[u8]> {
self.0.get(name).map(|v| v.as_slice())
}
}
struct TestParams {
grids: String,
}
impl crate::TransParamLookup for TestParams {
fn get_str(&self, key: &str) -> Option<&str> {
if key == "grids" {
Some(&self.grids)
} else {
None
}
}
fn get_f64(&self, _: &str) -> Option<f64> {
None
}
fn get_dms(&self, _: &str) -> Option<f64> {
None
}
fn get_int(&self, _: &str) -> Option<i64> {
None
}
fn get_bool(&self, _: &str) -> bool {
false
}
fn exists(&self, key: &str) -> bool {
key == "grids"
}
}
fn make_transform() -> TransBuild {
let mut map = std::collections::HashMap::new();
map.insert("test.json".to_string(), make_tinshift_json());
let reg = InMemReg(map);
let tp = TestParams {
grids: "test.json".to_string(),
};
let ell = oxiproj_core::Ellipsoid::named("WGS84").unwrap();
let params = crate::TransParams {
ellipsoid: &ell,
params: &tp,
registry: Some(®),
};
new(¶ms).unwrap()
}
#[test]
fn test_tinshift_forward_inside_mesh() {
let tb = make_transform();
let input = Coord::new(0.3 * DEG_TO_RAD, 0.3 * DEG_TO_RAD, 0.0, 0.0);
let out = tb.operation.forward_4d(input);
assert!(out.is_ok(), "should succeed inside mesh: {:?}", out);
let v = out.unwrap().v();
assert!(v[0] > 0.3 * DEG_TO_RAD, "lon should be shifted positively");
assert!(v[1] > 0.3 * DEG_TO_RAD, "lat should be shifted positively");
}
#[test]
fn test_tinshift_inverse_unavailable() {
let tb = make_transform();
assert!(!tb.operation.has_inverse());
let c = Coord::new(0.3 * DEG_TO_RAD, 0.3 * DEG_TO_RAD, 0.0, 0.0);
assert_eq!(
tb.operation.inverse_4d(c).err(),
Some(ProjError::NoInverseOp)
);
}
#[test]
fn test_tinshift_outside_returns_err() {
let tb = make_transform();
let far = Coord::new(45.0 * DEG_TO_RAD, 45.0 * DEG_TO_RAD, 0.0, 0.0);
assert_eq!(
tb.operation.forward_4d(far).err(),
Some(ProjError::OutsideGrid)
);
}
#[test]
fn test_tinshift_missing_grids_param() {
struct NoParams;
impl crate::TransParamLookup for NoParams {
fn get_str(&self, _: &str) -> Option<&str> {
None
}
fn get_f64(&self, _: &str) -> Option<f64> {
None
}
fn get_dms(&self, _: &str) -> Option<f64> {
None
}
fn get_int(&self, _: &str) -> Option<i64> {
None
}
fn get_bool(&self, _: &str) -> bool {
false
}
fn exists(&self, _: &str) -> bool {
false
}
}
let reg = InMemReg(std::collections::HashMap::new());
let ell = oxiproj_core::Ellipsoid::named("WGS84").unwrap();
let np = NoParams;
let params = crate::TransParams {
ellipsoid: &ell,
params: &np,
registry: Some(®),
};
assert_eq!(new(¶ms).err(), Some(ProjError::MissingArg));
}
}