use std::{
collections::{HashMap, HashSet, VecDeque},
fmt,
};
use nalgebra::{Matrix3, Vector3};
use strum::{Display, EnumIter, EnumString};
use crate::coords::{Coordinate, ECEF};
mod params;
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, EnumString, Display, EnumIter, Hash)]
#[strum(serialize_all = "UPPERCASE")]
pub enum ReferenceFrame {
ITRF88,
ITRF89,
ITRF90,
ITRF91,
ITRF92,
ITRF93,
ITRF94,
ITRF96,
ITRF97,
ITRF2000,
ITRF2005,
ITRF2008,
ITRF2014,
ITRF2020,
ETRF89,
ETRF90,
ETRF91,
ETRF92,
ETRF93,
ETRF94,
ETRF96,
ETRF97,
ETRF2000,
ETRF2005,
ETRF2014,
ETRF2020,
#[strum(to_string = "NAD83(2011)", serialize = "NAD83_2011")]
NAD83_2011,
#[allow(non_camel_case_types)]
#[strum(to_string = "NAD83(CSRS)", serialize = "NAD83_CSRS")]
NAD83_CSRS,
#[allow(non_camel_case_types)]
#[strum(to_string = "DREF91(R2016)", serialize = "DREF91_R2016")]
DREF91_R2016,
#[allow(non_camel_case_types)]
#[strum(to_string = "WGS84(G1762)", serialize = "WGS84_G1762")]
WGS84_G1762,
#[allow(non_camel_case_types)]
#[strum(to_string = "WGS84(G2139)", serialize = "WGS84_G2139")]
WGS84_G2139,
#[allow(non_camel_case_types)]
#[strum(to_string = "WGS84(G2296)", serialize = "WGS84_G2296")]
WGS84_G2296,
#[allow(non_camel_case_types)]
#[strum(to_string = "WGS84(G1674)", serialize = "WGS84_G1674")]
WGS84_G1674,
#[strum(transparent, default)]
Other(String),
}
impl PartialEq<&ReferenceFrame> for ReferenceFrame {
fn eq(&self, other: &&ReferenceFrame) -> bool {
self == *other
}
}
impl PartialEq<ReferenceFrame> for &ReferenceFrame {
fn eq(&self, other: &ReferenceFrame) -> bool {
*self == other
}
}
#[derive(Debug, PartialEq, PartialOrd, Clone, Copy)]
pub struct TimeDependentHelmertParams {
pub t: Vector3<f64>,
pub t_dot: Vector3<f64>,
pub s: f64,
pub s_dot: f64,
pub r: Vector3<f64>,
pub r_dot: Vector3<f64>,
pub epoch: f64,
}
impl TimeDependentHelmertParams {
pub const TRANSLATE_SCALE: f64 = 1.0e-3;
pub const SCALE_SCALE: f64 = 1.0e-9;
pub const ROTATE_SCALE: f64 = (std::f64::consts::PI / 180.0) * (0.001 / 3600.0);
#[must_use]
pub const fn zeros() -> TimeDependentHelmertParams {
TimeDependentHelmertParams {
t: Vector3::new(0.0, 0.0, 0.0),
t_dot: Vector3::new(0.0, 0.0, 0.0),
s: 0.0,
s_dot: 0.0,
r: Vector3::new(0.0, 0.0, 0.0),
r_dot: Vector3::new(0.0, 0.0, 0.0),
epoch: 0.0,
}
}
#[must_use]
pub fn invert(mut self) -> Self {
self.t *= -1.0;
self.t_dot *= -1.0;
self.s *= -1.0;
self.s_dot *= -1.0;
self.r *= -1.0;
self.r_dot *= -1.0;
self
}
#[must_use]
pub fn transform_position(&self, position: &ECEF, epoch: f64) -> ECEF {
let dt = epoch - self.epoch;
let t = (self.t + self.t_dot * dt) * Self::TRANSLATE_SCALE;
let s = (self.s + self.s_dot * dt) * Self::SCALE_SCALE;
let r = (self.r + self.r_dot * dt) * Self::ROTATE_SCALE;
let m = Self::make_rotation_matrix(s, r);
(position.as_vector() + t + m * position.as_vector()).into()
}
#[must_use]
pub fn transform_velocity(&self, velocity: &ECEF, position: &ECEF) -> ECEF {
let t = self.t_dot * Self::TRANSLATE_SCALE;
let s = self.s_dot * Self::SCALE_SCALE;
let r = self.r_dot * Self::ROTATE_SCALE;
let m = Self::make_rotation_matrix(s, r);
(velocity.as_vector() + t + m * position.as_vector()).into()
}
#[must_use]
fn make_rotation_matrix(s: f64, r: Vector3<f64>) -> Matrix3<f64> {
Matrix3::new(s, -r.z, r.y, r.z, s, -r.x, -r.y, r.x, s)
}
#[must_use]
pub fn transform(
&self,
position: &ECEF,
velocity: Option<&ECEF>,
epoch: f64,
) -> (ECEF, Option<ECEF>) {
let position = self.transform_position(position, epoch);
let velocity = velocity.map(|v| self.transform_velocity(v, &position));
(position, velocity)
}
}
impl Default for TimeDependentHelmertParams {
fn default() -> Self {
Self {
epoch: 2020.0,
..Self::zeros()
}
}
}
#[derive(Debug, PartialEq, PartialOrd, Clone)]
pub struct Transformation {
pub from: ReferenceFrame,
pub to: ReferenceFrame,
pub params: TimeDependentHelmertParams,
}
impl Transformation {
pub fn transform(&self, coord: &Coordinate) -> Result<Coordinate, TransformationNotFound> {
if coord.reference_frame() != self.from {
return Err(TransformationNotFound(
self.from.clone(),
coord.reference_frame().clone(),
));
}
let (new_position, new_velocity) = self.params.transform(
&coord.position(),
coord.velocity().as_ref(),
coord.epoch().to_fractional_year_hardcoded(),
);
Ok(Coordinate::new(
self.to.clone(),
new_position,
new_velocity,
coord.epoch(),
))
}
#[must_use]
pub fn invert(mut self) -> Self {
std::mem::swap(&mut self.from, &mut self.to);
self.params = self.params.invert();
self
}
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
pub struct TransformationNotFound(ReferenceFrame, ReferenceFrame);
impl fmt::Display for TransformationNotFound {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "No transformation found from {} to {}", self.0, self.1)
}
}
impl std::error::Error for TransformationNotFound {}
type TransformationGraph =
HashMap<ReferenceFrame, HashMap<ReferenceFrame, TimeDependentHelmertParams>>;
#[derive(Debug, Clone)]
pub struct TransformationRepository {
transformations: TransformationGraph,
}
impl TransformationRepository {
#[must_use]
pub fn new() -> Self {
Self {
transformations: TransformationGraph::new(),
}
}
pub fn from_transformations<T: IntoIterator<Item = Transformation>>(
transformations: T,
) -> Self {
let mut repo = Self::new();
repo.extend(transformations);
repo
}
#[must_use]
pub fn from_builtin() -> Self {
Self::from_transformations(builtin_transformations())
}
pub fn add_transformation(&mut self, transformation: Transformation) {
let from = transformation.from;
let to = transformation.to;
let params = transformation.params;
let inverted_params = params.invert();
self.transformations
.entry(from.clone())
.or_default()
.extend([(to.clone(), params)]);
self.transformations
.entry(to)
.or_default()
.extend([(from, inverted_params)]);
}
pub fn transform(
&self,
coord: &Coordinate,
to: &ReferenceFrame,
) -> Result<Coordinate, TransformationNotFound> {
let epoch = coord.epoch().to_fractional_year_hardcoded();
let (position, velocity) = self
.get_shortest_path(coord.reference_frame(), to)?
.into_iter()
.fold(
(coord.position(), coord.velocity()),
|(pos, vel), params| params.transform(&pos, vel.as_ref(), epoch),
);
Ok(Coordinate::new(
to.clone(),
position,
velocity,
coord.epoch(),
))
}
fn get_shortest_path(
&self,
from: &ReferenceFrame,
to: &ReferenceFrame,
) -> Result<Vec<&TimeDependentHelmertParams>, TransformationNotFound> {
if from == to {
return Ok(Vec::new());
}
let mut visited: HashSet<&ReferenceFrame> = HashSet::new();
let mut queue: VecDeque<(&ReferenceFrame, Vec<&TimeDependentHelmertParams>)> =
VecDeque::new();
queue.push_back((from, Vec::new()));
while let Some((current_frame, path)) = queue.pop_front() {
if current_frame == to {
return Ok(path);
}
if let Some(neighbors) = self.transformations.get(current_frame) {
for neighbor in neighbors {
if !visited.contains(neighbor.0) {
visited.insert(neighbor.0);
let mut new_path = path.clone();
new_path.push(neighbor.1);
queue.push_back((neighbor.0, new_path));
}
}
}
}
Err(TransformationNotFound(from.clone(), to.clone()))
}
#[must_use]
pub fn count(&self) -> usize {
self.transformations.values().map(HashMap::len).sum()
}
}
impl Default for TransformationRepository {
fn default() -> Self {
Self::from_builtin()
}
}
impl Extend<Transformation> for TransformationRepository {
fn extend<T: IntoIterator<Item = Transformation>>(&mut self, iter: T) {
iter.into_iter()
.for_each(|transformation| self.add_transformation(transformation));
}
}
#[must_use]
fn builtin_transformations() -> Vec<Transformation> {
params::TRANSFORMATIONS.to_vec()
}
#[cfg(test)]
mod tests {
use std::str::FromStr;
use float_eq::assert_float_eq;
use params::TRANSFORMATIONS;
use super::*;
#[expect(clippy::too_many_lines)]
#[test]
fn reference_frame_strings() {
assert_eq!(ReferenceFrame::ITRF88.to_string(), "ITRF88");
assert_eq!(
ReferenceFrame::from_str("ITRF88"),
Ok(ReferenceFrame::ITRF88)
);
assert_eq!(ReferenceFrame::ITRF89.to_string(), "ITRF89");
assert_eq!(
ReferenceFrame::from_str("ITRF89"),
Ok(ReferenceFrame::ITRF89)
);
assert_eq!(ReferenceFrame::ITRF90.to_string(), "ITRF90");
assert_eq!(
ReferenceFrame::from_str("ITRF90"),
Ok(ReferenceFrame::ITRF90)
);
assert_eq!(ReferenceFrame::ITRF91.to_string(), "ITRF91");
assert_eq!(
ReferenceFrame::from_str("ITRF91"),
Ok(ReferenceFrame::ITRF91)
);
assert_eq!(ReferenceFrame::ITRF92.to_string(), "ITRF92");
assert_eq!(
ReferenceFrame::from_str("ITRF92"),
Ok(ReferenceFrame::ITRF92)
);
assert_eq!(ReferenceFrame::ITRF93.to_string(), "ITRF93");
assert_eq!(
ReferenceFrame::from_str("ITRF93"),
Ok(ReferenceFrame::ITRF93)
);
assert_eq!(ReferenceFrame::ITRF94.to_string(), "ITRF94");
assert_eq!(
ReferenceFrame::from_str("ITRF94"),
Ok(ReferenceFrame::ITRF94)
);
assert_eq!(ReferenceFrame::ITRF96.to_string(), "ITRF96");
assert_eq!(
ReferenceFrame::from_str("ITRF96"),
Ok(ReferenceFrame::ITRF96)
);
assert_eq!(ReferenceFrame::ITRF97.to_string(), "ITRF97");
assert_eq!(
ReferenceFrame::from_str("ITRF97"),
Ok(ReferenceFrame::ITRF97)
);
assert_eq!(ReferenceFrame::ITRF2000.to_string(), "ITRF2000");
assert_eq!(
ReferenceFrame::from_str("ITRF2000"),
Ok(ReferenceFrame::ITRF2000)
);
assert_eq!(ReferenceFrame::ITRF2005.to_string(), "ITRF2005");
assert_eq!(
ReferenceFrame::from_str("ITRF2005"),
Ok(ReferenceFrame::ITRF2005)
);
assert_eq!(ReferenceFrame::ITRF2008.to_string(), "ITRF2008");
assert_eq!(
ReferenceFrame::from_str("ITRF2008"),
Ok(ReferenceFrame::ITRF2008)
);
assert_eq!(ReferenceFrame::ITRF2014.to_string(), "ITRF2014");
assert_eq!(
ReferenceFrame::from_str("ITRF2014"),
Ok(ReferenceFrame::ITRF2014)
);
assert_eq!(ReferenceFrame::ITRF2020.to_string(), "ITRF2020");
assert_eq!(
ReferenceFrame::from_str("ITRF2020"),
Ok(ReferenceFrame::ITRF2020)
);
assert_eq!(ReferenceFrame::ETRF89.to_string(), "ETRF89");
assert_eq!(
ReferenceFrame::from_str("ETRF89"),
Ok(ReferenceFrame::ETRF89)
);
assert_eq!(ReferenceFrame::ETRF90.to_string(), "ETRF90");
assert_eq!(
ReferenceFrame::from_str("ETRF90"),
Ok(ReferenceFrame::ETRF90)
);
assert_eq!(ReferenceFrame::ETRF91.to_string(), "ETRF91");
assert_eq!(
ReferenceFrame::from_str("ETRF91"),
Ok(ReferenceFrame::ETRF91)
);
assert_eq!(ReferenceFrame::ETRF92.to_string(), "ETRF92");
assert_eq!(
ReferenceFrame::from_str("ETRF92"),
Ok(ReferenceFrame::ETRF92)
);
assert_eq!(ReferenceFrame::ETRF93.to_string(), "ETRF93");
assert_eq!(
ReferenceFrame::from_str("ETRF93"),
Ok(ReferenceFrame::ETRF93)
);
assert_eq!(ReferenceFrame::ETRF94.to_string(), "ETRF94");
assert_eq!(
ReferenceFrame::from_str("ETRF94"),
Ok(ReferenceFrame::ETRF94)
);
assert_eq!(ReferenceFrame::ETRF96.to_string(), "ETRF96");
assert_eq!(
ReferenceFrame::from_str("ETRF96"),
Ok(ReferenceFrame::ETRF96)
);
assert_eq!(ReferenceFrame::ETRF97.to_string(), "ETRF97");
assert_eq!(
ReferenceFrame::from_str("ETRF97"),
Ok(ReferenceFrame::ETRF97)
);
assert_eq!(ReferenceFrame::ETRF2000.to_string(), "ETRF2000");
assert_eq!(
ReferenceFrame::from_str("ETRF2000"),
Ok(ReferenceFrame::ETRF2000)
);
assert_eq!(ReferenceFrame::ETRF2005.to_string(), "ETRF2005");
assert_eq!(
ReferenceFrame::from_str("ETRF2005"),
Ok(ReferenceFrame::ETRF2005)
);
assert_eq!(ReferenceFrame::ETRF2014.to_string(), "ETRF2014");
assert_eq!(
ReferenceFrame::from_str("ETRF2014"),
Ok(ReferenceFrame::ETRF2014)
);
assert_eq!(ReferenceFrame::ETRF2020.to_string(), "ETRF2020");
assert_eq!(
ReferenceFrame::from_str("ETRF2020"),
Ok(ReferenceFrame::ETRF2020)
);
assert_eq!(ReferenceFrame::NAD83_2011.to_string(), "NAD83(2011)");
assert_eq!(
ReferenceFrame::from_str("NAD83_2011"),
Ok(ReferenceFrame::NAD83_2011)
);
assert_eq!(
ReferenceFrame::from_str("NAD83(2011)"),
Ok(ReferenceFrame::NAD83_2011)
);
assert_eq!(ReferenceFrame::NAD83_CSRS.to_string(), "NAD83(CSRS)");
assert_eq!(
ReferenceFrame::from_str("NAD83_CSRS"),
Ok(ReferenceFrame::NAD83_CSRS)
);
assert_eq!(
ReferenceFrame::from_str("NAD83(CSRS)"),
Ok(ReferenceFrame::NAD83_CSRS)
);
}
#[test]
fn helmert_position_translations() {
let params = TimeDependentHelmertParams {
t: Vector3::new(
1.0 / TimeDependentHelmertParams::TRANSLATE_SCALE,
2.0 / TimeDependentHelmertParams::TRANSLATE_SCALE,
3.0 / TimeDependentHelmertParams::TRANSLATE_SCALE,
),
t_dot: Vector3::new(
0.1 / TimeDependentHelmertParams::TRANSLATE_SCALE,
0.2 / TimeDependentHelmertParams::TRANSLATE_SCALE,
0.3 / TimeDependentHelmertParams::TRANSLATE_SCALE,
),
s: 0.0,
s_dot: 0.0,
r: Vector3::new(0.0, 0.0, 0.0),
r_dot: Vector3::new(0.0, 0.0, 0.0),
epoch: 2010.0,
};
let initial_position = ECEF::default();
let transformed_position = params.transform_position(&initial_position, 2010.0);
assert_float_eq!(transformed_position.x(), 1.0, abs_all <= 1e-4);
assert_float_eq!(transformed_position.y(), 2.0, abs_all <= 1e-4);
assert_float_eq!(transformed_position.z(), 3.0, abs_all <= 1e-4);
let transformed_position = params.transform_position(&initial_position, 2011.0);
assert_float_eq!(transformed_position.x(), 1.1, abs_all <= 1e-4);
assert_float_eq!(transformed_position.y(), 2.2, abs_all <= 1e-4);
assert_float_eq!(transformed_position.z(), 3.3, abs_all <= 1e-4);
}
#[test]
fn helmert_position_scaling() {
let params = TimeDependentHelmertParams {
t: Vector3::new(0.0, 0.0, 0.0),
t_dot: Vector3::new(0.0, 0.0, 0.0),
s: 1.0 / TimeDependentHelmertParams::SCALE_SCALE,
s_dot: 0.1 / TimeDependentHelmertParams::SCALE_SCALE,
r: Vector3::new(90.0, 0.0, 0.0),
r_dot: Vector3::new(0.0, 0.0, 0.0),
epoch: 2010.0,
};
let initial_position = ECEF::new(1., 2., 3.);
let transformed_position = params.transform_position(&initial_position, 2010.0);
assert_float_eq!(transformed_position.x(), 2.0, abs_all <= 1e-4);
assert_float_eq!(transformed_position.y(), 4.0, abs_all <= 1e-4);
assert_float_eq!(transformed_position.z(), 6.0, abs_all <= 1e-4);
let transformed_position = params.transform_position(&initial_position, 2011.0);
assert_float_eq!(transformed_position.x(), 2.1, abs_all <= 1e-4);
assert_float_eq!(transformed_position.y(), 4.2, abs_all <= 1e-4);
assert_float_eq!(transformed_position.z(), 6.3, abs_all <= 1e-4);
}
#[test]
fn helmert_position_rotations() {
let params = TimeDependentHelmertParams {
t: Vector3::new(0.0, 0.0, 0.0),
t_dot: Vector3::new(0.0, 0.0, 0.0),
s: 0.0,
s_dot: 0.0,
r: Vector3::new(
1.0 / TimeDependentHelmertParams::ROTATE_SCALE,
2.0 / TimeDependentHelmertParams::ROTATE_SCALE,
3.0 / TimeDependentHelmertParams::ROTATE_SCALE,
),
r_dot: Vector3::new(
0.1 / TimeDependentHelmertParams::ROTATE_SCALE,
0.2 / TimeDependentHelmertParams::ROTATE_SCALE,
0.3 / TimeDependentHelmertParams::ROTATE_SCALE,
),
epoch: 2010.0,
};
let initial_position = ECEF::new(1.0, 1.0, 1.0);
let transformed_position = params.transform_position(&initial_position, 2010.0);
assert_float_eq!(transformed_position.x(), 0.0, abs_all <= 1e-4);
assert_float_eq!(transformed_position.y(), 3.0, abs_all <= 1e-4);
assert_float_eq!(transformed_position.z(), 0.0, abs_all <= 1e-4);
let transformed_position = params.transform_position(&initial_position, 2011.0);
assert_float_eq!(transformed_position.x(), -0.1, abs_all <= 1e-9);
assert_float_eq!(transformed_position.y(), 3.2, abs_all <= 1e-9);
assert_float_eq!(transformed_position.z(), -0.1, abs_all <= 1e-9);
}
#[test]
fn helmert_velocity_translations() {
let params = TimeDependentHelmertParams {
t: Vector3::new(
1.0 / TimeDependentHelmertParams::TRANSLATE_SCALE,
2.0 / TimeDependentHelmertParams::TRANSLATE_SCALE,
3.0 / TimeDependentHelmertParams::TRANSLATE_SCALE,
),
t_dot: Vector3::new(
0.1 / TimeDependentHelmertParams::TRANSLATE_SCALE,
0.2 / TimeDependentHelmertParams::TRANSLATE_SCALE,
0.3 / TimeDependentHelmertParams::TRANSLATE_SCALE,
),
s: 0.0,
s_dot: 0.0,
r: Vector3::new(0.0, 0.0, 0.0),
r_dot: Vector3::new(0.0, 0.0, 0.0),
epoch: 2010.0,
};
let initial_velocity = ECEF::default();
let position = ECEF::default();
let transformed_velocity = params.transform_velocity(&initial_velocity, &position);
assert_float_eq!(transformed_velocity.x(), 0.1, abs_all <= 1e-4);
assert_float_eq!(transformed_velocity.y(), 0.2, abs_all <= 1e-4);
assert_float_eq!(transformed_velocity.z(), 0.3, abs_all <= 1e-4);
}
#[test]
fn helmert_velocity_scaling() {
let params = TimeDependentHelmertParams {
t: Vector3::new(0.0, 0.0, 0.0),
t_dot: Vector3::new(0.0, 0.0, 0.0),
s: 1.0 / TimeDependentHelmertParams::SCALE_SCALE,
s_dot: 0.1 / TimeDependentHelmertParams::SCALE_SCALE,
r: Vector3::new(90.0, 0.0, 0.0),
r_dot: Vector3::new(0.0, 0.0, 0.0),
epoch: 2010.0,
};
let initial_velocity = ECEF::default();
let position = ECEF::new(1., 2., 3.);
let transformed_velocity = params.transform_velocity(&initial_velocity, &position);
assert_float_eq!(transformed_velocity.x(), 0.1, abs_all <= 1e-4);
assert_float_eq!(transformed_velocity.y(), 0.2, abs_all <= 1e-4);
assert_float_eq!(transformed_velocity.z(), 0.3, abs_all <= 1e-4);
}
#[test]
fn helmert_velocity_rotations() {
let params = TimeDependentHelmertParams {
t: Vector3::new(0.0, 0.0, 0.0),
t_dot: Vector3::new(0.0, 0.0, 0.0),
s: 0.0,
s_dot: 0.0,
r: Vector3::new(
1.0 / TimeDependentHelmertParams::ROTATE_SCALE,
2.0 / TimeDependentHelmertParams::ROTATE_SCALE,
3.0 / TimeDependentHelmertParams::ROTATE_SCALE,
),
r_dot: Vector3::new(
0.1 / TimeDependentHelmertParams::ROTATE_SCALE,
0.2 / TimeDependentHelmertParams::ROTATE_SCALE,
0.3 / TimeDependentHelmertParams::ROTATE_SCALE,
),
epoch: 2010.0,
};
let initial_velocity = ECEF::default();
let position = ECEF::new(4., 5., 6.);
let transformed_velocity = params.transform_velocity(&initial_velocity, &position);
assert_float_eq!(transformed_velocity.x(), -0.3, abs_all <= 1e-4);
assert_float_eq!(transformed_velocity.y(), 0.6, abs_all <= 1e-4);
assert_float_eq!(transformed_velocity.z(), -0.3, abs_all <= 1e-4);
}
#[test]
fn helmert_invert() {
let params = TimeDependentHelmertParams {
t: Vector3::new(1.0, 2.0, 3.0),
t_dot: Vector3::new(0.1, 0.2, 0.3),
s: 4.0,
s_dot: 0.4,
r: Vector3::new(5.0, 6.0, 7.0),
r_dot: Vector3::new(0.5, 0.6, 0.7),
epoch: 2010.0,
}
.invert();
assert_float_eq!(params.t.x, -1.0, abs_all <= 1e-4);
assert_float_eq!(params.t_dot.x, -0.1, abs_all <= 1e-4);
assert_float_eq!(params.t.y, -2.0, abs_all <= 1e-4);
assert_float_eq!(params.t_dot.y, -0.2, abs_all <= 1e-4);
assert_float_eq!(params.t.z, -3.0, abs_all <= 1e-4);
assert_float_eq!(params.t_dot.z, -0.3, abs_all <= 1e-4);
assert_float_eq!(params.s, -4.0, abs_all <= 1e-4);
assert_float_eq!(params.s_dot, -0.4, abs_all <= 1e-4);
assert_float_eq!(params.r.x, -5.0, abs_all <= 1e-4);
assert_float_eq!(params.r_dot.x, -0.5, abs_all <= 1e-4);
assert_float_eq!(params.r.y, -6.0, abs_all <= 1e-4);
assert_float_eq!(params.r_dot.y, -0.6, abs_all <= 1e-4);
assert_float_eq!(params.r.z, -7.0, abs_all <= 1e-4);
assert_float_eq!(params.r_dot.z, -0.7, abs_all <= 1e-4);
assert_float_eq!(params.epoch, 2010.0, abs_all <= 1e-4);
let params = params.invert();
assert_float_eq!(params.t.x, 1.0, abs_all <= 1e-4);
assert_float_eq!(params.t_dot.x, 0.1, abs_all <= 1e-4);
assert_float_eq!(params.t.y, 2.0, abs_all <= 1e-4);
assert_float_eq!(params.t_dot.y, 0.2, abs_all <= 1e-4);
assert_float_eq!(params.t.z, 3.0, abs_all <= 1e-4);
assert_float_eq!(params.t_dot.z, 0.3, abs_all <= 1e-4);
assert_float_eq!(params.s, 4.0, abs_all <= 1e-4);
assert_float_eq!(params.s_dot, 0.4, abs_all <= 1e-4);
assert_float_eq!(params.r.x, 5.0, abs_all <= 1e-4);
assert_float_eq!(params.r_dot.x, 0.5, abs_all <= 1e-4);
assert_float_eq!(params.r.y, 6.0, abs_all <= 1e-4);
assert_float_eq!(params.r_dot.y, 0.6, abs_all <= 1e-4);
assert_float_eq!(params.r.z, 7.0, abs_all <= 1e-4);
assert_float_eq!(params.r_dot.z, 0.7, abs_all <= 1e-4);
assert_float_eq!(params.epoch, 2010.0, abs_all <= 1e-4);
}
#[test]
fn itrf2020_to_etrf2000_shortest_path() {
let from = ReferenceFrame::ITRF2020;
let to = ReferenceFrame::ETRF2000;
assert!(!TRANSFORMATIONS.iter().any(|t| t.from == from && t.to == to));
let graph: TransformationRepository = TransformationRepository::from_builtin();
let path = graph.get_shortest_path(&from, &to);
let path = path.unwrap();
assert_eq!(path.len(), 2);
}
#[test]
fn transformation_repository_empty() {
let repo = TransformationRepository::new();
assert_eq!(repo.count(), 0);
let result = repo.get_shortest_path(&ReferenceFrame::ITRF2020, &ReferenceFrame::ITRF2014);
result.unwrap_err();
}
#[test]
fn transformation_repository_from_builtin() {
let repo = TransformationRepository::from_builtin();
assert_eq!(repo.count(), TRANSFORMATIONS.len() * 2);
let path = repo.get_shortest_path(&ReferenceFrame::ITRF2020, &ReferenceFrame::ETRF2000);
path.unwrap();
}
#[test]
fn transformation_repository_add_transformation() {
let mut repo = TransformationRepository::new();
let transformation = Transformation {
from: ReferenceFrame::ITRF2020,
to: ReferenceFrame::ITRF2014,
params: TimeDependentHelmertParams {
t: Vector3::new(1.0, 2.0, 3.0),
t_dot: Vector3::new(0.0, 0.0, 0.0),
s: 0.0,
s_dot: 0.0,
r: Vector3::new(0.0, 0.0, 0.0),
r_dot: Vector3::new(0.0, 0.0, 0.0),
epoch: 2015.0,
},
};
let params = transformation.params;
repo.add_transformation(transformation);
assert_eq!(repo.count(), 2);
let result = repo.get_shortest_path(&ReferenceFrame::ITRF2020, &ReferenceFrame::ITRF2014);
assert_eq!(result.unwrap(), vec![¶ms]);
let params = params.invert();
let reverse_result =
repo.get_shortest_path(&ReferenceFrame::ITRF2014, &ReferenceFrame::ITRF2020);
assert_eq!(reverse_result.unwrap(), vec![¶ms]);
}
#[test]
fn transformation_repository_from_transformations() {
let transformations = vec![
Transformation {
from: ReferenceFrame::ITRF2020,
to: ReferenceFrame::ITRF2014,
params: TimeDependentHelmertParams {
t: Vector3::new(1.0, 2.0, 3.0),
t_dot: Vector3::new(0.0, 0.0, 0.0),
s: 0.0,
s_dot: 0.0,
r: Vector3::new(0.0, 0.0, 0.0),
r_dot: Vector3::new(0.0, 0.0, 0.0),
epoch: 2015.0,
},
},
Transformation {
from: ReferenceFrame::ITRF2014,
to: ReferenceFrame::ITRF2000,
params: TimeDependentHelmertParams {
t: Vector3::new(4.0, 5.0, 6.0),
t_dot: Vector3::new(0.0, 0.0, 0.0),
s: 0.0,
s_dot: 0.0,
r: Vector3::new(0.0, 0.0, 0.0),
r_dot: Vector3::new(0.0, 0.0, 0.0),
epoch: 2015.0,
},
},
];
let repo = TransformationRepository::from_transformations(transformations.clone());
assert_eq!(repo.count(), 4);
let result = repo.get_shortest_path(&ReferenceFrame::ITRF2020, &ReferenceFrame::ITRF2014);
result.unwrap();
let path = repo.get_shortest_path(&ReferenceFrame::ITRF2020, &ReferenceFrame::ITRF2000);
let path = path.unwrap();
assert_eq!(path.len(), 2); assert_eq!(
path,
vec![&transformations[0].params, &transformations[1].params]
);
}
#[test]
fn custom_reference_frame_creation() {
let custom_frame = ReferenceFrame::Other("MyLocalFrame".to_string());
assert_eq!(custom_frame.to_string(), "MyLocalFrame");
}
#[test]
fn custom_reference_frame_from_str() {
let custom_frame: ReferenceFrame = "UnknownFrame".parse().unwrap();
assert_eq!(
custom_frame,
ReferenceFrame::Other("UnknownFrame".to_string())
);
assert_eq!(custom_frame.to_string(), "UnknownFrame");
}
#[test]
fn known_reference_frame_from_str() {
let itrf_frame: ReferenceFrame = "ITRF2020".parse().unwrap();
assert_eq!(itrf_frame, ReferenceFrame::ITRF2020);
let nad83_frame: ReferenceFrame = "NAD83(2011)".parse().unwrap();
assert_eq!(nad83_frame, ReferenceFrame::NAD83_2011);
let nad83_frame2: ReferenceFrame = "NAD83_2011".parse().unwrap();
assert_eq!(nad83_frame2, ReferenceFrame::NAD83_2011);
}
#[test]
fn custom_transformation() {
let transformation = Transformation {
from: ReferenceFrame::ITRF2020,
to: ReferenceFrame::Other("LocalFrame".to_string()),
params: TimeDependentHelmertParams {
t: Vector3::new(1.0, 2.0, 3.0),
t_dot: Vector3::new(0.0, 0.0, 0.0),
s: 0.0,
s_dot: 0.0,
r: Vector3::new(0.0, 0.0, 0.0),
r_dot: Vector3::new(0.0, 0.0, 0.0),
epoch: 2020.0,
},
};
assert_eq!(transformation.from, ReferenceFrame::ITRF2020);
assert_eq!(
transformation.to,
ReferenceFrame::Other("LocalFrame".to_string())
);
}
#[test]
fn custom_transformation_repository() {
let mut repo = TransformationRepository::new();
let transformation = Transformation {
from: ReferenceFrame::Other("Frame1".to_string()),
to: ReferenceFrame::Other("Frame2".to_string()),
params: TimeDependentHelmertParams {
t: Vector3::new(1.0, 2.0, 3.0),
t_dot: Vector3::new(0.0, 0.0, 0.0),
s: 0.0,
s_dot: 0.0,
r: Vector3::new(0.0, 0.0, 0.0),
r_dot: Vector3::new(0.0, 0.0, 0.0),
epoch: 2020.0,
},
};
repo.add_transformation(transformation);
let result = repo.get_shortest_path(
&ReferenceFrame::Other("Frame1".to_string()),
&ReferenceFrame::Other("Frame2".to_string()),
);
result.unwrap();
let reverse_result = repo.get_shortest_path(
&ReferenceFrame::Other("Frame2".to_string()),
&ReferenceFrame::Other("Frame1".to_string()),
);
reverse_result.unwrap();
}
}