use crate::{TransBuild, TransParams};
use oxiproj_core::{Ellipsoid, IoUnits, Lp, Lpz, Operation, ProjResult, Xy, Xyz};
#[derive(Debug)]
struct Geocent {
ell: Ellipsoid,
}
impl Operation for Geocent {
fn forward_2d(&self, lp: Lp) -> ProjResult<Xy> {
let xyz = crate::internal::geodetic_to_cartesian(Lpz::new(lp.lam, lp.phi, 0.0), &self.ell);
Ok(Xy::new(xyz.x, xyz.y))
}
fn inverse_2d(&self, xy: Xy) -> ProjResult<Lp> {
let lpz = crate::internal::cartesian_to_geodetic(Xyz::new(xy.x, xy.y, 0.0), &self.ell);
Ok(Lp::new(lpz.lam, lpz.phi))
}
fn forward_3d(&self, lpz: Lpz) -> ProjResult<Xyz> {
Ok(crate::internal::geodetic_to_cartesian(lpz, &self.ell))
}
fn inverse_3d(&self, xyz: Xyz) -> ProjResult<Lpz> {
Ok(crate::internal::cartesian_to_geodetic(xyz, &self.ell))
}
fn has_inverse(&self) -> bool {
true
}
}
pub fn new(p: &TransParams) -> ProjResult<TransBuild> {
Ok(TransBuild::new(
Box::new(Geocent { ell: *p.ellipsoid }),
IoUnits::Radians,
IoUnits::Cartesian,
))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::TransParamLookup;
use oxiproj_core::{Coord, Ellipsoid, DEG_TO_RAD};
struct NoParams;
impl TransParamLookup for NoParams {
fn get_dms(&self, _key: &str) -> Option<f64> {
None
}
fn get_f64(&self, _key: &str) -> Option<f64> {
None
}
fn get_int(&self, _key: &str) -> Option<i64> {
None
}
fn get_str(&self, _key: &str) -> Option<&str> {
None
}
fn get_bool(&self, _key: &str) -> bool {
false
}
fn exists(&self, _key: &str) -> bool {
false
}
}
fn build_wgs84() -> TransBuild {
let ell = Ellipsoid::named("WGS84").unwrap();
let np = NoParams;
let tp = TransParams {
ellipsoid: &ell,
params: &np,
registry: None,
};
new(&tp).unwrap()
}
#[test]
fn geocent_build_units() {
let b = build_wgs84();
assert_eq!(b.left, IoUnits::Radians);
assert_eq!(b.right, IoUnits::Cartesian);
}
#[test]
fn geocent_forward_matches_proj_ecef() {
let op = build_wgs84().operation;
let out = op
.forward_3d(Lpz::new(12.0 * DEG_TO_RAD, 55.0 * DEG_TO_RAD, 100.0))
.unwrap();
assert!((out.x - 3586525.761017917).abs() < 1e-4, "X = {}", out.x);
assert!((out.y - 762339.584102928).abs() < 1e-4, "Y = {}", out.y);
assert!((out.z - 5201465.438406702).abs() < 1e-4, "Z = {}", out.z);
}
#[test]
fn geocent_inverse_matches_proj_geodetic() {
let op = build_wgs84().operation;
let lpz = op
.inverse_3d(Xyz::new(
3586525.761017917,
762339.584102928,
5201465.438406702,
))
.unwrap();
assert!(
(lpz.lam - 12.0 * DEG_TO_RAD).abs() < 1e-9,
"lam = {}",
lpz.lam
);
assert!(
(lpz.phi - 55.0 * DEG_TO_RAD).abs() < 1e-9,
"phi = {}",
lpz.phi
);
assert!((lpz.z - 100.0).abs() < 1e-4, "z = {}", lpz.z);
}
#[test]
fn geocent_round_trip_preserves_time() {
let op = build_wgs84().operation;
let input = Coord::new(12.0 * DEG_TO_RAD, 55.0 * DEG_TO_RAD, 100.0, 2020.5);
let ecef = op.forward_4d(input).unwrap();
assert!(
ecef.v()[0] > 3.0e6,
"X should be ECEF metres, got {}",
ecef.v()[0]
);
assert_eq!(ecef.v()[3], 2020.5, "time must pass through");
let back = op.inverse_4d(ecef).unwrap();
assert!((back.v()[0] - input.v()[0]).abs() < 1e-9);
assert!((back.v()[1] - input.v()[1]).abs() < 1e-9);
assert!((back.v()[2] - input.v()[2]).abs() < 1e-4);
assert_eq!(back.v()[3], 2020.5);
}
}