diff --git a/swiftnav/src/coords/mod.rs b/swiftnav/src/coords/mod.rs index 7de9a18..3ba4a72 100644 --- a/swiftnav/src/coords/mod.rs +++ b/swiftnav/src/coords/mod.rs @@ -84,10 +84,7 @@ pub use ellipsoid::*; pub use llh::*; pub use ned::*; -use crate::{ - reference_frame::{get_transformation, ReferenceFrame, TransformationNotFound}, - time::GpsTime, -}; +use crate::{reference_frame::ReferenceFrame, time::GpsTime}; use nalgebra::Vector2; /// WGS84 local horizontal coordinates consisting of an Azimuth and Elevation, with angles stored as radians @@ -193,7 +190,7 @@ impl AsMut> for AzimuthElevation { /// Complete coordinate used for transforming between reference frames /// /// Velocities are optional, but when present they will be transformed -#[derive(Debug, PartialEq, PartialOrd, Clone, Copy)] +#[derive(Debug, PartialEq, PartialOrd, Clone)] pub struct Coordinate { reference_frame: ReferenceFrame, position: ECEF, @@ -218,7 +215,7 @@ impl Coordinate { } } - /// Create a new [`Coordinate`] object with a velocity value + /// Create a new [`Coordinate`] object with no velocity value #[must_use] pub fn without_velocity( reference_frame: ReferenceFrame, @@ -233,7 +230,7 @@ impl Coordinate { } } - /// Create a new [`Coordinate`] object with no velocity + /// Create a new [`Coordinate`] object with a velocity #[must_use] pub fn with_velocity( reference_frame: ReferenceFrame, @@ -251,8 +248,8 @@ impl Coordinate { /// Get the reference frame of the coordinate #[must_use] - pub fn reference_frame(&self) -> ReferenceFrame { - self.reference_frame + pub fn reference_frame(&self) -> &ReferenceFrame { + &self.reference_frame } /// Get the position of the coordinate @@ -276,7 +273,7 @@ impl Coordinate { /// Use the velocity term to adjust the epoch of the coordinate. /// When a coordinate has no velocity the position won't be changed. #[must_use] - pub fn adjust_epoch(&self, new_epoch: &GpsTime) -> Self { + pub fn adjust_epoch(self, new_epoch: &GpsTime) -> Self { let dt = new_epoch.to_fractional_year_hardcoded() - self.epoch.to_fractional_year_hardcoded(); let v = self.velocity.unwrap_or_default(); @@ -288,17 +285,6 @@ impl Coordinate { reference_frame: self.reference_frame, } } - - /// Transform the coordinate from into a new reference frame - /// - /// # Errors - /// - /// An error is returned if a transformation from the coordinate's reference frame to the requested - /// reference frame could not be found. - pub fn transform_to(&self, new_frame: ReferenceFrame) -> Result { - get_transformation(self.reference_frame, new_frame) - .map(|transformation| transformation.transform(self)) - } } #[cfg(test)] @@ -519,7 +505,7 @@ mod tests { initial_epoch, ); - let new_coord = initial_coord.adjust_epoch(&new_epoch); + let new_coord = initial_coord.clone().adjust_epoch(&new_epoch); assert_eq!(initial_coord.reference_frame, new_coord.reference_frame); assert_float_eq!(new_coord.position.x(), 1.0, abs <= 0.001); diff --git a/swiftnav/src/reference_frame/mod.rs b/swiftnav/src/reference_frame/mod.rs index b78df34..9b2e95f 100644 --- a/swiftnav/src/reference_frame/mod.rs +++ b/swiftnav/src/reference_frame/mod.rs @@ -9,70 +9,64 @@ // WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. //! Geodetic reference frame transformations //! -//! Geodetic reference frames define the coordinate system used to represent -//! positions on the Earth. Different reference frames are commonly used in -//! different regions of the world, and for different purposes. For example, -//! global reference frames, such as the International Terrestrial Reference -//! Frame (ITRF), are used for global positioning, while regional reference -//! frames, such as the European Terrestrial Reference Frame (ETRF), are used -//! for regional positioning. Due to the movement of the earth's crust apparently -//! fixed positions will move over time. Because of this it's important to note -//! only take note of a position, but also the time at which that position was -//! determined. In most regions of the earth the crust moves at a constant speed, -//! meaning that if you are able to determine the local velocity of the crust you -//! can easily determine what the position of a static point would have been in -//! the past. It is commong for regional reference frames to define a common reference -//! epoch that all positions should be transformed to, allowing the direct comparison -//! of positions even if they were determined at different times. Regional reference -//! frames also typically are defined to be "fixed" to a particular tectonic plate, -//! meaning the large majority of the velocity for points on that tectonic plate -//! are cancelled out. In contrast, global reference frames are not fixed to -//! any particular tectonic plate, so most places on earth will have a measurable -//! velocity. Global reference frames also typically do not have a common reference -//! epoch, so determining one's local velocity is important to be able to compare -//! positions or to transform a coordinate from a global reference frame to a regional -//! reference frame. +//! Transform coordinates between geodetic reference frames using time-dependent Helmert transformations. +//! Supports both global frames (ITRF series) and regional frames (ETRF, NAD83, etc.) with runtime +//! parameter loading. //! -//! This module provides several types and functions to help transform a set of coordinates -//! from one reference frame to another, and from one epoch to another. Several sets of -//! transformation parameters are included for converting between common reference frames. -//! To start out, you must have a [`Coordinate`] that you want to transform. This consists of a -//! position, an epoch, and a reference frame as well as an optional velocity. You then need to -//! get the [`Transformation`] object that describes the transformation from the reference -//! frame of the coordinate to the desired reference frame. You can then call the `transform` -//! method on the transformation object to get a new coordinate in the desired reference frame. -//! This transformation will change the position and velocity of the coordinate, but it does -//! not the change the epoch of the coordinate. If you need to change the epoch of the -//! coordinate you will need to use the [`Coordinate::adjust_epoch`](crate::coords::Coordinate::adjust_epoch) -//! method which uses the velocity of the coordinate to determine the position at the new epoch. +//! # Key Concepts +//! +//! - **Reference frames** define coordinate systems for Earth positioning +//! - **Crustal motion** causes positions to change over time, requiring velocity tracking +//! - **Transformations** use 15-parameter Helmert models with time dependencies +//! - **Path finding** automatically chains transformations between frames +//! +//! # Core Types +//! +//! - [`ReferenceFrame`] - Enumeration of supported coordinate systems +//! - [`TransformationRepository`] - Manages transformation parameters and pathfinding +//! - [`Coordinate`] - Position with reference frame, epoch, and optional velocity +//! - [`TimeDependentHelmertParams`] - 15-parameter transformation model +//! +//! # Basic Usage //! -//! # Example //! ``` //! use swiftnav::{ //! coords::{Coordinate, ECEF}, -//! reference_frame::{get_transformation, ReferenceFrame, TransformationNotFound}, +//! reference_frame::{TransformationRepository, ReferenceFrame}, //! time::UtcTime //! }; //! -//! let transformation = get_transformation(ReferenceFrame::ITRF2014, ReferenceFrame::NAD83_2011) -//! .unwrap(); +//! let repo = TransformationRepository::from_builtin(); +//! let epoch = UtcTime::from_parts(2020, 3, 15, 0, 0, 0.).to_gps_hardcoded(); //! -//! let epoch_2020 = UtcTime::from_parts(2020, 3, 15, 0, 0, 0.).to_gps_hardcoded(); +//! // Create coordinate with position and velocity //! let itrf_coord = Coordinate::with_velocity( -//! ReferenceFrame::ITRF2014, // The reference frame of the coordinate -//! ECEF::new(-2703764.0, -4261273.0, 3887158.0), // The position of the coordinate -//! ECEF::new(-0.221, 0.254, 0.122), // The velocity of the coordinate -//! epoch_2020); // The epoch of the coordinate +//! ReferenceFrame::ITRF2014, +//! ECEF::new(-2703764.0, -4261273.0, 3887158.0), +//! ECEF::new(-0.221, 0.254, 0.122), +//! epoch +//! ); +//! +//! // Transform to different reference frame +//! let nad83_coord = repo.transform(&itrf_coord, &ReferenceFrame::NAD83_2011)?; +//! # Ok::<(), Box>(()) +//! ``` //! -//! let epoch_2010 = UtcTime::from_parts(2010, 1, 1, 0, 0, 0.).to_gps_hardcoded(); -//! let itrf_coord = itrf_coord.adjust_epoch(&epoch_2010); // Change the epoch of the coordinate +//! # Custom Transformations //! -//! let nad83_coord = transformation.transform(&itrf_coord); -//! // Alternatively, you can use the `transform_to` method on the coordinate itself -//! let nad83_coord: Result = -//! itrf_coord.transform_to(ReferenceFrame::NAD83_2011); //! ``` +//! # use nalgebra::Vector3; +//! # use swiftnav::reference_frame::*; +//! let mut repo = TransformationRepository::new(); +//! +//! let custom_transform = Transformation { +//! from: ReferenceFrame::ITRF2020, +//! to: ReferenceFrame::Other("LOCAL_FRAME".to_string()), +//! params: TimeDependentHelmertParams::default(), +//! }; //! +//! repo.add_transformation(custom_transform); +//! ``` use crate::coords::{Coordinate, ECEF}; use nalgebra::{Matrix3, Vector3}; @@ -84,10 +78,30 @@ use strum::{Display, EnumIter, EnumString}; mod params; -/// Reference Frames -#[derive( - Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, EnumString, Display, EnumIter, Hash, -)] +/// Geodetic reference frame identifiers +/// +/// Enumerates well-known global and regional reference frames with support +/// for custom frames via the [`Other`] variant. +/// +/// # Examples +/// ``` +/// # use swiftnav::reference_frame::ReferenceFrame; +/// # use std::str::FromStr; +/// // Use predefined frames +/// let itrf = ReferenceFrame::ITRF2020; +/// let nad83 = ReferenceFrame::NAD83_2011; +/// +/// // Parse from string +/// let parsed: ReferenceFrame = "ITRF2014".parse()?; +/// let custom: ReferenceFrame = "MY_LOCAL_FRAME".parse()?; +/// +/// // Custom frames +/// let local = ReferenceFrame::Other("SITE_FRAME_2023".to_string()); +/// # Ok::<(), Box>(()) +/// ``` +/// +/// [`Other`]: ReferenceFrame::Other +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, EnumString, Display, EnumIter, Hash)] #[strum(serialize_all = "UPPERCASE")] pub enum ReferenceFrame { ITRF88, @@ -135,6 +149,21 @@ pub enum ReferenceFrame { #[allow(non_camel_case_types)] #[strum(to_string = "WGS84(G2296)", serialize = "WGS84_G2296")] WGS84_G2296, + /// Custom reference frame with user-defined name + #[strum(transparent, default)] + Other(String), +} + +impl PartialEq<&ReferenceFrame> for ReferenceFrame { + fn eq(&self, other: &&ReferenceFrame) -> bool { + self == *other + } +} + +impl PartialEq for &ReferenceFrame { + fn eq(&self, other: &ReferenceFrame) -> bool { + *self == other + } } /// 15-parameter Helmert transformation parameters @@ -163,37 +192,86 @@ pub enum ReferenceFrame { /// Where $p$ is the constant value, $\dot{p}$ is the rate of /// change, and $\tau$ is the reference epoch. /// +/// # Parameter Units +/// +/// Input parameters are stored in standard geodetic units: +/// - **Translation** (`tx`, `ty`, `tz`): millimeters (mm) +/// - **Translation rates** (`tx_dot`, `ty_dot`, `tz_dot`): mm/year +/// - **Scale** (`s`): parts per billion (ppb) +/// - **Scale rate** (`s_dot`): ppb/year +/// - **Rotation** (`rx`, `ry`, `rz`): milliarcseconds (mas) +/// - **Rotation rates** (`rx_dot`, `ry_dot`, `rz_dot`): mas/year +/// - **Reference epoch** (`epoch`): decimal years +/// +/// These units are automatically converted to SI units during computation. +/// +/// # Sign Convention +/// /// There are several sign conventions in use for the rotation /// parameters in Helmert transformations. In this implementation /// we follow the IERS conventions, which is opposite of the original /// formulation of the Helmert transformation. #[derive(Debug, PartialEq, PartialOrd, Clone, Copy)] pub struct TimeDependentHelmertParams { - t: Vector3, - t_dot: Vector3, - s: f64, - s_dot: f64, - r: Vector3, - r_dot: Vector3, - epoch: f64, + pub t: Vector3, + pub t_dot: Vector3, + pub s: f64, + pub s_dot: f64, + pub r: Vector3, + pub r_dot: Vector3, + pub epoch: f64, } impl TimeDependentHelmertParams { - const TRANSLATE_SCALE: f64 = 1.0e-3; - const SCALE_SCALE: f64 = 1.0e-9; - const ROTATE_SCALE: f64 = (std::f64::consts::PI / 180.0) * (0.001 / 3600.0); + /// Scale factor for translation parameters (converts mm to m) + pub const TRANSLATE_SCALE: f64 = 1.0e-3; + /// Scale factor for scale parameters (converts ppb to unit scale) + pub const SCALE_SCALE: f64 = 1.0e-9; + /// Scale factor for rotation parameters (converts milliarcseconds to radians) + pub const ROTATE_SCALE: f64 = (std::f64::consts::PI / 180.0) * (0.001 / 3600.0); + + /// Create a [`TimeDependentHelmertParams`] object with zero in every field. + /// + /// # Note + /// + /// The [`TimeDependentHelmertParams::epoch`] field needs to represent a real time, and an + /// epoch of `0.0` is almost certainly not what you intend. This is best used to initialize + /// all other fields to zero like this: + /// + /// ```rust + /// # use swiftnav::reference_frame::TimeDependentHelmertParams; + /// let params = TimeDependentHelmertParams { + /// epoch: 2020.0, + /// ..TimeDependentHelmertParams::zeros() + /// }; + /// ``` + #[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, + } + } /// Reverses the transformation. Since this is a linear transformation we simply negate all terms - pub fn invert(&mut self) { + #[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 } - /// Apply the transformation on a position at a specific epoch + /// Apply the transformation to a position at a specific epoch #[must_use] pub fn transform_position(&self, position: &ECEF, epoch: f64) -> ECEF { let dt = epoch - self.epoch; @@ -206,7 +284,7 @@ impl TimeDependentHelmertParams { (position.as_vector() + t + m * position.as_vector()).into() } - /// Apply the transformation on a velocity at a specific position + /// Apply the transformation to a velocity at a specific position #[must_use] pub fn transform_velocity(&self, velocity: &ECEF, position: &ECEF) -> ECEF { let t = self.t_dot * Self::TRANSLATE_SCALE; @@ -222,10 +300,44 @@ impl TimeDependentHelmertParams { fn make_rotation_matrix(s: f64, r: Vector3) -> Matrix3 { Matrix3::new(s, -r.z, r.y, r.z, s, -r.x, -r.y, r.x, s) } + + /// Apply the complete Helmert transformation to position and velocity + /// + /// Combines position and velocity transformations into a single operation. + /// The velocity transformation uses the rate terms from the Helmert parameters. + /// + /// # Arguments + /// * `position` - Position to transform + /// * `velocity` - Optional velocity to transform (None preserves None) + /// * `epoch` - Time epoch for position transformation + /// + /// # Returns + /// Tuple of transformed (position, velocity) + #[must_use] + pub fn transform( + &self, + position: &ECEF, + velocity: Option<&ECEF>, + epoch: f64, + ) -> (ECEF, Option) { + 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() + } + } } /// A transformation from one reference frame to another. -#[derive(Debug, PartialEq, PartialOrd, Clone, Copy)] +#[derive(Debug, PartialEq, PartialOrd, Clone)] pub struct Transformation { pub from: ReferenceFrame, pub to: ReferenceFrame, @@ -238,33 +350,37 @@ impl Transformation { /// Reference frame transformations do not change the epoch of the /// coordinate. /// - /// # Panics + /// # Errors /// - /// This function will panic if the given coordinate is not already in the reference - /// frame this [`Transformation`] converts from. - #[must_use] - pub fn transform(&self, coord: &Coordinate) -> Coordinate { - assert!( - coord.reference_frame() == self.from, - "Coordinate reference frame does not match transformation from reference frame" - ); + /// [`TransformationNotFound`] Is returned as an error if there is a mismatch between + /// the coordinate reference frame and the [`Transformation::from`] field. + pub fn transform(&self, coord: &Coordinate) -> Result { + if coord.reference_frame() != self.from { + return Err(TransformationNotFound( + self.from.clone(), + coord.reference_frame().clone(), + )); + } - let new_position = self.params.transform_position( + let (new_position, new_velocity) = self.params.transform( &coord.position(), + coord.velocity().as_ref(), coord.epoch().to_fractional_year_hardcoded(), ); - let new_velocity = coord - .velocity() - .as_ref() - .map(|velocity| self.params.transform_velocity(velocity, &coord.position())); - Coordinate::new(self.to, new_position, new_velocity, coord.epoch()) + + Ok(Coordinate::new( + self.to.clone(), + new_position, + new_velocity, + coord.epoch(), + )) } /// Reverse the transformation #[must_use] pub fn invert(mut self) -> Self { std::mem::swap(&mut self.from, &mut self.to); - self.params.invert(); + self.params = self.params.invert(); self } } @@ -273,7 +389,7 @@ impl Transformation { /// /// This error is returned when trying to find a transformation between two reference frames /// and no transformation is found. -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)] +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)] pub struct TransformationNotFound(ReferenceFrame, ReferenceFrame); impl fmt::Display for TransformationNotFound { @@ -284,108 +400,184 @@ impl fmt::Display for TransformationNotFound { impl std::error::Error for TransformationNotFound {} -/// Find a transformation from one reference frame to another -/// -/// We currently only support a limited set of transformations. -/// If no transformation is found, `None` is returned. -/// -/// # Errors -/// -/// An error will be returned if a transformation between the two reference -/// frames couldn't be found. -pub fn get_transformation( - from: ReferenceFrame, - to: ReferenceFrame, -) -> Result { - params::TRANSFORMATIONS - .iter() - .find(|t| (t.from == from && t.to == to) || (t.from == to && t.to == from)) - .map(|t| { - if t.from == from && t.to == to { - *t - } else { - (*t).invert() - } - }) - .ok_or(TransformationNotFound(from, to)) -} +type TransformationGraph = + HashMap>; -/// A helper type for finding transformations between reference frames that require multiple steps +/// A repository for managing reference frame transformations /// -/// This object can be used to determine which calls to [`get_transformation`] -/// are needed when a single transformation does not exist between two reference frames. -pub struct TransformationGraph { - graph: HashMap>, +/// This struct allows for loading the builtin transformations +/// as well as adding additional transformations from other sources. +#[derive(Debug, Clone)] +pub struct TransformationRepository { + transformations: TransformationGraph, } -impl TransformationGraph { - /// Create a new transformation graph, fully populated with the known transformations +impl TransformationRepository { + /// Create an empty transformation repository + #[must_use] pub fn new() -> Self { - let mut graph = HashMap::new(); - for transformation in ¶ms::TRANSFORMATIONS { - graph - .entry(transformation.from) - .or_insert_with(HashSet::new) - .insert(transformation.to); - graph - .entry(transformation.to) - .or_insert_with(HashSet::new) - .insert(transformation.from); + Self { + transformations: TransformationGraph::new(), } - TransformationGraph { graph } } - /// Get the shortest path between two reference frames, if one exists + /// Create a repository from a list of transformations + /// + /// # Note /// - /// This function will also search for reverse paths if no direct path is found. - /// The search is performed breadth-first. + /// If there are duplicated transformations in the list, the + /// last one in the list will take priority + pub fn from_transformations>( + transformations: T, + ) -> Self { + let mut repo = Self::new(); + repo.extend(transformations); + repo + } + + /// Create a repository with the builtin transformations #[must_use] - pub fn get_shortest_path( + pub fn from_builtin() -> Self { + Self::from_transformations(builtin_transformations()) + } + + /// Add a transformation to the repository + /// + /// This will rebuild the internal graph to include the new transformation. + 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)]); + + // Add inverted parameters as well + self.transformations + .entry(to) + .or_default() + .extend([(from, inverted_params)]); + } + + /// Transform a [`Coordinate`] to a new reference frame + /// + /// This function finds the shortest series of transformations from the coordinate's + /// initial reference frame to the requestest one, then sequentially applies + /// those transformations to get the new position and velocity. The epoch of the + /// coordinate is not modified in this process. + /// + /// # Errors + /// + /// [`TransformationNotFound`] is returned as an error if no path from the + /// coordinate's reference frame to the requested reference frame could be found + /// in the repository. + pub fn transform( &self, - from: ReferenceFrame, - to: ReferenceFrame, - ) -> Option> { + coord: &Coordinate, + to: &ReferenceFrame, + ) -> Result { + 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(), + )) + } + + /// Find the shortest transformation path between reference frames + /// + /// Uses breadth-first search to find the minimal sequence of transformations + /// needed to convert between reference frames. The algorithm automatically + /// chains transformations when no direct path exists. + /// + /// Returns an empty vector if source and destination frames are identical. + /// + /// # Errors + /// + /// [`TransformationNotFound`] if no transformation path exists between the frames + fn get_shortest_path( + &self, + from: &ReferenceFrame, + to: &ReferenceFrame, + ) -> Result, TransformationNotFound> { if from == to { - return None; + return Ok(Vec::new()); } - let mut visited: HashSet = HashSet::new(); - let mut queue: VecDeque<(ReferenceFrame, Vec)> = VecDeque::new(); - queue.push_back((from, vec![from])); + 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 Some(path); + return Ok(path); } - if let Some(neighbors) = self.graph.get(¤t_frame) { + if let Some(neighbors) = self.transformations.get(current_frame) { for neighbor in neighbors { - if !visited.contains(neighbor) { - visited.insert(*neighbor); + if !visited.contains(neighbor.0) { + visited.insert(neighbor.0); let mut new_path = path.clone(); - new_path.push(*neighbor); - queue.push_back((*neighbor, new_path)); + new_path.push(neighbor.1); + queue.push_back((neighbor.0, new_path)); } } } } - None + + Err(TransformationNotFound(from.clone(), to.clone())) + } + + /// Get the number of transformations stored in the repository + #[must_use] + pub fn count(&self) -> usize { + self.transformations.values().map(HashMap::len).sum() } } -impl Default for TransformationGraph { +impl Default for TransformationRepository { fn default() -> Self { - TransformationGraph::new() + Self::from_builtin() + } +} + +impl Extend for TransformationRepository { + fn extend>(&mut self, iter: T) { + iter.into_iter() + .for_each(|transformation| self.add_transformation(transformation)); } } +/// Get the builtin transformation parameters +/// +/// Returns a Vec of all pre-defined transformations between common reference frames +/// including ITRF, ETRF, NAD83, and WGS84 series. These transformations are sourced +/// from authoritative geodetic organizations.. +#[must_use] +fn builtin_transformations() -> Vec { + params::TRANSFORMATIONS.to_vec() +} + #[cfg(test)] mod tests { use super::*; use float_eq::assert_float_eq; use params::TRANSFORMATIONS; use std::str::FromStr; - use strum::IntoEnumIterator; #[test] fn reference_frame_strings() { @@ -705,7 +897,7 @@ mod tests { #[test] fn helmert_invert() { - let mut params = TimeDependentHelmertParams { + 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, @@ -713,14 +905,15 @@ mod tests { r: Vector3::new(5.0, 6.0, 7.0), r_dot: Vector3::new(0.5, 0.6, 0.7), epoch: 2010.0, - }; - params.invert(); + } + .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); @@ -730,13 +923,14 @@ mod tests { 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); - params.invert(); + 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); @@ -756,29 +950,194 @@ mod tests { // Make sure there isn't a direct path assert!(!TRANSFORMATIONS.iter().any(|t| t.from == from && t.to == to)); - let graph = TransformationGraph::new(); - let path = graph.get_shortest_path(from, to); - assert!(path.is_some()); + let graph: TransformationRepository = TransformationRepository::from_builtin(); + let path = graph.get_shortest_path(&from, &to); // Make sure that the path is correct. N.B. this may change if more transformations // are added in the future let path = path.unwrap(); - assert_eq!(path.len(), 3); - assert_eq!(path[0], from); - assert_eq!(path[1], ReferenceFrame::ITRF2000); - assert_eq!(path[2], to); + assert_eq!(path.len(), 2); } #[test] - fn fully_traversable_graph() { - let graph = TransformationGraph::new(); - for from in ReferenceFrame::iter() { - for to in ReferenceFrame::iter() { - if from == to { - continue; - } - let path = graph.get_shortest_path(from, to); - assert!(path.is_some(), "No path from {} to {}", from, to); - } - } + 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); // Also cound inverted transformations + + // Test path finding + let path = repo.get_shortest_path(&ReferenceFrame::ITRF2020, &ReferenceFrame::ETRF2000); + path.unwrap(); + } + + #[test] + fn transformation_repository_add_transformation() { + let mut repo = TransformationRepository::new(); + + // Create a simple transformation for testing + 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.clone(); + 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]); + + // Test reverse transformation + 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); + + // Test direct transformation + let result = repo.get_shortest_path(&ReferenceFrame::ITRF2020, &ReferenceFrame::ITRF2014); + result.unwrap(); + + // Test multi-step path + let path = repo.get_shortest_path(&ReferenceFrame::ITRF2020, &ReferenceFrame::ITRF2000); + let path = path.unwrap(); + assert_eq!(path.len(), 2); // Two transformations: ITRF2020->ITRF2014 and ITRF2014->ITRF2000 + 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(); } } diff --git a/swiftnav/src/reference_frame/params.rs b/swiftnav/src/reference_frame/params.rs index 3649019..fe2bf3f 100644 --- a/swiftnav/src/reference_frame/params.rs +++ b/swiftnav/src/reference_frame/params.rs @@ -2,7 +2,7 @@ use nalgebra::Vector3; use super::{ReferenceFrame, TimeDependentHelmertParams, Transformation}; -pub const TRANSFORMATIONS: [Transformation; 34] = [ +pub const TRANSFORMATIONS: [Transformation; 33] = [ Transformation { from: ReferenceFrame::ITRF2020, to: ReferenceFrame::ITRF2014, @@ -10,10 +10,8 @@ pub const TRANSFORMATIONS: [Transformation; 34] = [ t: Vector3::new(-1.4, -0.9, 1.4), t_dot: Vector3::new(0.0, -0.1, 0.2), s: -0.42, - 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, + ..TimeDependentHelmertParams::zeros() }, }, Transformation { @@ -24,9 +22,8 @@ pub const TRANSFORMATIONS: [Transformation; 34] = [ t_dot: Vector3::new(0.0, -0.1, 0.1), s: -0.29, s_dot: 0.03, - r: Vector3::new(0.0, 0.0, 0.0), - r_dot: Vector3::new(0.0, 0.0, 0.0), epoch: 2015.0, + ..TimeDependentHelmertParams::zeros() }, }, Transformation { @@ -37,9 +34,8 @@ pub const TRANSFORMATIONS: [Transformation; 34] = [ t_dot: Vector3::new(0.3, -0.1, 0.1), s: 0.65, s_dot: 0.03, - r: Vector3::new(0.0, 0.0, 0.0), - r_dot: Vector3::new(0.0, 0.0, 0.0), epoch: 2015.0, + ..TimeDependentHelmertParams::zeros() }, }, Transformation { @@ -50,9 +46,8 @@ pub const TRANSFORMATIONS: [Transformation; 34] = [ t_dot: Vector3::new(0.1, 0.0, -1.7), s: 2.25, s_dot: 0.11, - r: Vector3::new(0.0, 0.0, 0.0), - r_dot: Vector3::new(0.0, 0.0, 0.0), epoch: 2015.0, + ..TimeDependentHelmertParams::zeros() }, }, Transformation { @@ -176,26 +171,18 @@ pub const TRANSFORMATIONS: [Transformation; 34] = [ from: ReferenceFrame::ITRF2020, to: ReferenceFrame::ETRF2020, 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(0.0, 0.0, 0.0), r_dot: Vector3::new(0.086, 0.519, -0.753), epoch: 1989.0, + ..TimeDependentHelmertParams::zeros() }, }, Transformation { from: ReferenceFrame::ITRF2014, to: ReferenceFrame::ETRF2014, 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(0.0, 0.0, 0.0), r_dot: Vector3::new(0.085, 0.531, -0.770), epoch: 1989.0, + ..TimeDependentHelmertParams::zeros() }, }, Transformation { @@ -203,12 +190,9 @@ pub const TRANSFORMATIONS: [Transformation; 34] = [ to: ReferenceFrame::ETRF2005, params: TimeDependentHelmertParams { t: Vector3::new(56.0, 48.0, -37.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.054, 0.518, -0.781), epoch: 1989.0, + ..TimeDependentHelmertParams::zeros() }, }, Transformation { @@ -216,12 +200,9 @@ pub const TRANSFORMATIONS: [Transformation; 34] = [ to: ReferenceFrame::ETRF2000, params: TimeDependentHelmertParams { t: Vector3::new(54.0, 51.0, -48.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.081, 0.490, -0.792), epoch: 1989.0, + ..TimeDependentHelmertParams::zeros() }, }, Transformation { @@ -229,12 +210,9 @@ pub const TRANSFORMATIONS: [Transformation; 34] = [ to: ReferenceFrame::ETRF97, params: TimeDependentHelmertParams { t: Vector3::new(41.0, 41.0, -49.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.200, 0.500, -0.650), epoch: 1989.0, + ..TimeDependentHelmertParams::zeros() }, }, Transformation { @@ -242,12 +220,9 @@ pub const TRANSFORMATIONS: [Transformation; 34] = [ to: ReferenceFrame::ETRF96, params: TimeDependentHelmertParams { t: Vector3::new(41.0, 41.0, -49.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.200, 0.500, -0.650), epoch: 1989.0, + ..TimeDependentHelmertParams::zeros() }, }, Transformation { @@ -255,12 +230,9 @@ pub const TRANSFORMATIONS: [Transformation; 34] = [ to: ReferenceFrame::ETRF94, params: TimeDependentHelmertParams { t: Vector3::new(41.0, 41.0, -49.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.200, 0.500, -0.650), epoch: 1989.0, + ..TimeDependentHelmertParams::zeros() }, }, Transformation { @@ -268,12 +240,9 @@ pub const TRANSFORMATIONS: [Transformation; 34] = [ to: ReferenceFrame::ETRF93, params: TimeDependentHelmertParams { t: Vector3::new(19.0, 53.0, -21.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.320, 0.780, -0.670), epoch: 1989.0, + ..TimeDependentHelmertParams::zeros() }, }, Transformation { @@ -281,12 +250,9 @@ pub const TRANSFORMATIONS: [Transformation; 34] = [ to: ReferenceFrame::ETRF92, params: TimeDependentHelmertParams { t: Vector3::new(38.0, 40.0, -37.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.210, 0.520, -0.680), epoch: 1989.0, + ..TimeDependentHelmertParams::zeros() }, }, Transformation { @@ -294,12 +260,9 @@ pub const TRANSFORMATIONS: [Transformation; 34] = [ to: ReferenceFrame::ETRF91, params: TimeDependentHelmertParams { t: Vector3::new(21.0, 25.0, -37.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.210, 0.520, -0.680), epoch: 1989.0, + ..TimeDependentHelmertParams::zeros() }, }, Transformation { @@ -307,25 +270,18 @@ pub const TRANSFORMATIONS: [Transformation; 34] = [ to: ReferenceFrame::ETRF90, params: TimeDependentHelmertParams { t: Vector3::new(19.0, 28.0, -23.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.110, 0.570, -0.710), epoch: 1989.0, + ..TimeDependentHelmertParams::zeros() }, }, Transformation { from: ReferenceFrame::ITRF89, to: ReferenceFrame::ETRF89, 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(0.0, 0.0, 0.0), r_dot: Vector3::new(0.110, 0.570, -0.710), epoch: 1989.0, + ..TimeDependentHelmertParams::zeros() }, }, Transformation { @@ -341,19 +297,6 @@ pub const TRANSFORMATIONS: [Transformation; 34] = [ epoch: 2010.0, }, }, - Transformation { - from: ReferenceFrame::ITRF2014, - to: ReferenceFrame::ETRF2014, - 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(0.0, 0.0, 0.0), - r_dot: Vector3::new(0.085, 0.531, -0.770), - epoch: 1989.0, - }, - }, Transformation { from: ReferenceFrame::ITRF2008, to: ReferenceFrame::NAD83_CSRS, @@ -411,13 +354,8 @@ pub const TRANSFORMATIONS: [Transformation; 34] = [ from: ReferenceFrame::ITRF2020, to: ReferenceFrame::WGS84_G2296, 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(0.0, 0.0, 0.0), - r_dot: Vector3::new(0.0, 0.0, 0.0), epoch: 2024.0, + ..TimeDependentHelmertParams::zeros() }, }, // WGS84(G2139) is defined to be the same as ITRF2014 at epoch 2016.0 @@ -425,13 +363,8 @@ pub const TRANSFORMATIONS: [Transformation; 34] = [ from: ReferenceFrame::ITRF2014, to: ReferenceFrame::WGS84_G2139, 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(0.0, 0.0, 0.0), - r_dot: Vector3::new(0.0, 0.0, 0.0), epoch: 2016.0, + ..TimeDependentHelmertParams::zeros() }, }, // WGS84(G1762) is defined to be the same as ITRF2008 at epoch 2005.0 @@ -439,13 +372,8 @@ pub const TRANSFORMATIONS: [Transformation; 34] = [ from: ReferenceFrame::ITRF2008, to: ReferenceFrame::WGS84_G1762, 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(0.0, 0.0, 0.0), - r_dot: Vector3::new(0.0, 0.0, 0.0), epoch: 2005.0, + ..TimeDependentHelmertParams::zeros() }, }, ]; diff --git a/swiftnav/tests/reference_frames.rs b/swiftnav/tests/reference_frames.rs index 009e3c1..0bcbf3d 100644 --- a/swiftnav/tests/reference_frames.rs +++ b/swiftnav/tests/reference_frames.rs @@ -1,7 +1,7 @@ use float_eq::assert_float_eq; use swiftnav::{ coords::{Coordinate, ECEF}, - reference_frame::{get_transformation, ReferenceFrame}, + reference_frame::{ReferenceFrame, TransformationRepository}, time::{GpsTime, UtcTime}, }; @@ -18,9 +18,11 @@ fn euref_itrf2014() { Some(ECEF::new(0.01, 0.2, 0.030)), make_epoch(2000), ); - let transformation = - get_transformation(ReferenceFrame::ITRF2020, ReferenceFrame::ITRF2014).unwrap(); - let result_coords = transformation.transform(&initial_coords); + let transformations = TransformationRepository::from_builtin(); + + let result_coords = transformations + .transform(&initial_coords, &ReferenceFrame::ITRF2014) + .unwrap(); assert_float_eq!(result_coords.position().x(), 4027894.0029, abs <= 0.0001); assert_float_eq!(result_coords.position().y(), 307045.6005, abs <= 0.0001); assert_float_eq!(result_coords.position().z(), 4919474.9063, abs <= 0.0001); @@ -53,9 +55,11 @@ fn euref_itrf2008() { Some(ECEF::new(0.01, 0.2, 0.030)), make_epoch(2000), ); - let transformation = - get_transformation(ReferenceFrame::ITRF2020, ReferenceFrame::ITRF2008).unwrap(); - let result_coords = transformation.transform(&initial_coords); + let transformations = TransformationRepository::from_builtin(); + + let result_coords = transformations + .transform(&initial_coords, &ReferenceFrame::ITRF2008) + .unwrap(); assert_float_eq!(result_coords.position().x(), 4027894.0032, abs <= 0.0001); assert_float_eq!(result_coords.position().y(), 307045.6023, abs <= 0.0001); assert_float_eq!(result_coords.position().z(), 4919474.9082, abs <= 0.0001); @@ -88,9 +92,11 @@ fn euref_etrf2020() { Some(ECEF::new(0.01, 0.2, 0.030)), make_epoch(2000), ); - let transformation = - get_transformation(ReferenceFrame::ITRF2020, ReferenceFrame::ETRF2020).unwrap(); - let result_coords = transformation.transform(&initial_coords); + let transformations = TransformationRepository::from_builtin(); + + let result_coords = transformations + .transform(&initial_coords, &ReferenceFrame::ETRF2020) + .unwrap(); assert_float_eq!(result_coords.position().x(), 4027894.1545, abs <= 0.0001); assert_float_eq!(result_coords.position().y(), 307045.4157, abs <= 0.0001); assert_float_eq!(result_coords.position().z(), 4919474.7999, abs <= 0.0001); @@ -123,9 +129,11 @@ fn euref_etrf2014() { Some(ECEF::new(0.01, 0.2, 0.030)), make_epoch(2000), ); - let transformation = - get_transformation(ReferenceFrame::ITRF2014, ReferenceFrame::ETRF2014).unwrap(); - let result_coords = transformation.transform(&initial_coords); + let transformations = TransformationRepository::from_builtin(); + + let result_coords = transformations + .transform(&initial_coords, &ReferenceFrame::ETRF2014) + .unwrap(); assert_float_eq!(result_coords.position().x(), 4027894.1579, abs <= 0.0001); assert_float_eq!(result_coords.position().y(), 307045.4123, abs <= 0.0001); assert_float_eq!(result_coords.position().z(), 4919474.7973, abs <= 0.0001); @@ -158,10 +166,19 @@ fn euref_etrf2005() { Some(ECEF::new(0.01, 0.2, 0.030)), make_epoch(2000), ); - let transformation = - get_transformation(ReferenceFrame::ITRF2005, ReferenceFrame::ETRF2005).unwrap(); - let result_coords = transformation.transform(&initial_coords); - assert_float_eq!(result_coords.position().x(), 4027894.2107, abs <= 0.0001); + let transformations = TransformationRepository::from_builtin(); + + let result_coords = transformations + .transform(&initial_coords.clone(), &ReferenceFrame::ETRF2005) + .unwrap(); + assert_float_eq!( + result_coords.position().x(), + 4027894.2107, + abs <= 0.0001, + "Initial: {:?} Result: {:?}", + initial_coords, + result_coords + ); assert_float_eq!(result_coords.position().y(), 307045.4661, abs <= 0.0001); assert_float_eq!(result_coords.position().z(), 4919474.7626, abs <= 0.0001); assert!(result_coords.velocity().is_some()); @@ -193,9 +210,11 @@ fn euref_etrf2000() { Some(ECEF::new(0.01, 0.2, 0.030)), make_epoch(2000), ); - let transformation = - get_transformation(ReferenceFrame::ITRF2000, ReferenceFrame::ETRF2000).unwrap(); - let result_coords = transformation.transform(&initial_coords); + let transformations = TransformationRepository::from_builtin(); + + let result_coords = transformations + .transform(&initial_coords, &ReferenceFrame::ETRF2000) + .unwrap(); assert_float_eq!(result_coords.position().x(), 4027894.2015, abs <= 0.0001); assert_float_eq!(result_coords.position().y(), 307045.4596, abs <= 0.0001); assert_float_eq!(result_coords.position().z(), 4919474.7581, abs <= 0.0001); @@ -228,9 +247,11 @@ fn euref_etrf97() { Some(ECEF::new(0.01, 0.2, 0.030)), make_epoch(2000), ); - let transformation = - get_transformation(ReferenceFrame::ITRF97, ReferenceFrame::ETRF97).unwrap(); - let result_coords = transformation.transform(&initial_coords); + let transformations = TransformationRepository::from_builtin(); + + let result_coords = transformations + .transform(&initial_coords, &ReferenceFrame::ETRF97) + .unwrap(); assert_float_eq!(result_coords.position().x(), 4027894.1888, abs <= 0.0001); assert_float_eq!(result_coords.position().y(), 307045.4489, abs <= 0.0001); assert_float_eq!(result_coords.position().z(), 4919474.7569, abs <= 0.0001); @@ -263,9 +284,11 @@ fn euref_etrf96() { Some(ECEF::new(0.01, 0.2, 0.030)), make_epoch(2000), ); - let transformation = - get_transformation(ReferenceFrame::ITRF96, ReferenceFrame::ETRF96).unwrap(); - let result_coords = transformation.transform(&initial_coords); + let transformations = TransformationRepository::from_builtin(); + + let result_coords = transformations + .transform(&initial_coords, &ReferenceFrame::ETRF96) + .unwrap(); assert_float_eq!(result_coords.position().x(), 4027894.1888, abs <= 0.0001); assert_float_eq!(result_coords.position().y(), 307045.4489, abs <= 0.0001); assert_float_eq!(result_coords.position().z(), 4919474.7569, abs <= 0.0001); @@ -298,9 +321,11 @@ fn euref_etrf94() { Some(ECEF::new(0.01, 0.2, 0.030)), make_epoch(2000), ); - let transformation = - get_transformation(ReferenceFrame::ITRF94, ReferenceFrame::ETRF94).unwrap(); - let result_coords = transformation.transform(&initial_coords); + let transformations = TransformationRepository::from_builtin(); + + let result_coords = transformations + .transform(&initial_coords, &ReferenceFrame::ETRF94) + .unwrap(); assert_float_eq!(result_coords.position().x(), 4027894.1888, abs <= 0.0001); assert_float_eq!(result_coords.position().y(), 307045.4489, abs <= 0.0001); assert_float_eq!(result_coords.position().z(), 4919474.7569, abs <= 0.0001); @@ -333,9 +358,11 @@ fn euref_etrf93() { Some(ECEF::new(0.01, 0.2, 0.030)), make_epoch(2000), ); - let transformation = - get_transformation(ReferenceFrame::ITRF93, ReferenceFrame::ETRF93).unwrap(); - let result_coords = transformation.transform(&initial_coords); + let transformations = TransformationRepository::from_builtin(); + + let result_coords = transformations + .transform(&initial_coords, &ReferenceFrame::ETRF93) + .unwrap(); assert_float_eq!(result_coords.position().x(), 4027894.2406, abs <= 0.0001); assert_float_eq!(result_coords.position().y(), 307045.4251, abs <= 0.0001); assert_float_eq!(result_coords.position().z(), 4919474.7267, abs <= 0.0001); @@ -368,9 +395,11 @@ fn euref_etrf92() { Some(ECEF::new(0.01, 0.2, 0.030)), make_epoch(2000), ); - let transformation = - get_transformation(ReferenceFrame::ITRF92, ReferenceFrame::ETRF92).unwrap(); - let result_coords = transformation.transform(&initial_coords); + let transformations = TransformationRepository::from_builtin(); + + let result_coords = transformations + .transform(&initial_coords, &ReferenceFrame::ETRF92) + .unwrap(); assert_float_eq!(result_coords.position().x(), 4027894.1916, abs <= 0.0001); assert_float_eq!(result_coords.position().y(), 307045.4388, abs <= 0.0001); assert_float_eq!(result_coords.position().z(), 4919474.7647, abs <= 0.0001); @@ -403,9 +432,11 @@ fn euref_etrf91() { Some(ECEF::new(0.01, 0.2, 0.030)), make_epoch(2000), ); - let transformation = - get_transformation(ReferenceFrame::ITRF91, ReferenceFrame::ETRF91).unwrap(); - let result_coords = transformation.transform(&initial_coords); + let transformations = TransformationRepository::from_builtin(); + + let result_coords = transformations + .transform(&initial_coords, &ReferenceFrame::ETRF91) + .unwrap(); assert_float_eq!(result_coords.position().x(), 4027894.1746, abs <= 0.0001); assert_float_eq!(result_coords.position().y(), 307045.4238, abs <= 0.0001); assert_float_eq!(result_coords.position().z(), 4919474.7647, abs <= 0.0001); @@ -438,9 +469,11 @@ fn euref_etrf90() { Some(ECEF::new(0.01, 0.2, 0.030)), make_epoch(2000), ); - let transformation = - get_transformation(ReferenceFrame::ITRF90, ReferenceFrame::ETRF90).unwrap(); - let result_coords = transformation.transform(&initial_coords); + let transformations = TransformationRepository::from_builtin(); + + let result_coords = transformations + .transform(&initial_coords, &ReferenceFrame::ETRF90) + .unwrap(); assert_float_eq!(result_coords.position().x(), 4027894.1862, abs <= 0.0001); assert_float_eq!(result_coords.position().y(), 307045.4466, abs <= 0.0001); assert_float_eq!(result_coords.position().z(), 4919474.7664, abs <= 0.0001); @@ -473,9 +506,11 @@ fn euref_etrf89() { Some(ECEF::new(0.01, 0.2, 0.030)), make_epoch(2000), ); - let transformation = - get_transformation(ReferenceFrame::ITRF89, ReferenceFrame::ETRF89).unwrap(); - let result_coords = transformation.transform(&initial_coords); + let transformations = TransformationRepository::from_builtin(); + + let result_coords = transformations + .transform(&initial_coords, &ReferenceFrame::ETRF89) + .unwrap(); assert_float_eq!(result_coords.position().x(), 4027894.1672, abs <= 0.0001); assert_float_eq!(result_coords.position().y(), 307045.4186, abs <= 0.0001); assert_float_eq!(result_coords.position().z(), 4919474.7894, abs <= 0.0001); @@ -508,9 +543,11 @@ fn euref_etrf2014_reverse() { Some(ECEF::new(0.01, 0.2, 0.030)), make_epoch(2000), ); - let transformation = - get_transformation(ReferenceFrame::ETRF2014, ReferenceFrame::ITRF2014).unwrap(); - let result_coords = transformation.transform(&initial_coords); + let transformations = TransformationRepository::from_builtin(); + + let result_coords = transformations + .transform(&initial_coords, &ReferenceFrame::ITRF2014) + .unwrap(); assert_float_eq!(result_coords.position().x(), 4027893.8541, abs <= 0.0001); assert_float_eq!(result_coords.position().y(), 307045.7877, abs <= 0.0001); assert_float_eq!(result_coords.position().z(), 4919475.0227, abs <= 0.0001); @@ -576,11 +613,15 @@ fn euref_complete_transform() { Some(ECEF::new(0.01, 0.2, 0.030)), make_epoch(2000), ); - let transformation = - get_transformation(ReferenceFrame::ITRF2014, ReferenceFrame::ETRF2014).unwrap(); + let transformations = TransformationRepository::from_builtin(); // Test adjusting the epoch first then transforming - let result_coords = transformation.transform(&initial_coords.adjust_epoch(&make_epoch(2008))); + let result_coords = transformations + .transform( + &initial_coords.clone().adjust_epoch(&make_epoch(2008)), + &ReferenceFrame::ETRF2014, + ) + .unwrap(); assert_float_eq!(result_coords.position().x(), 4027894.3484, abs <= 0.0001); assert_float_eq!(result_coords.position().y(), 307046.8758, abs <= 0.0001); assert_float_eq!(result_coords.position().z(), 4919474.9554, abs <= 0.0001); @@ -604,8 +645,9 @@ fn euref_complete_transform() { assert_eq!(result_coords.reference_frame(), ReferenceFrame::ETRF2014); // Test transforming first then adjusting the epoch - let result_coords = transformation - .transform(&initial_coords) + let result_coords = transformations + .transform(&initial_coords, &ReferenceFrame::ETRF2014) + .unwrap() .adjust_epoch(&make_epoch(2008)); assert_float_eq!(result_coords.position().x(), 4027894.3484, abs <= 0.0001); assert_float_eq!(result_coords.position().y(), 307046.8758, abs <= 0.0001); @@ -640,10 +682,11 @@ fn htdp_nad83_2011_fixed_date() { make_epoch(2010), ); - let transformation = - get_transformation(ReferenceFrame::ITRF2014, ReferenceFrame::NAD83_2011).unwrap(); + let transformations = TransformationRepository::from_builtin(); - let result_coords = transformation.transform(&initial_coords); + let result_coords = transformations + .transform(&initial_coords, &ReferenceFrame::NAD83_2011) + .unwrap(); assert_float_eq!(result_coords.position().x(), -2705104.572, abs <= 0.001); assert_float_eq!(result_coords.position().y(), -4262047.032, abs <= 0.001); assert_float_eq!(result_coords.position().z(), 3885381.705, abs <= 0.001); @@ -677,10 +720,14 @@ fn htdp_nad83_2011_adjust_epoch() { make_epoch(2020), ); - let transformation = - get_transformation(ReferenceFrame::ITRF2014, ReferenceFrame::NAD83_2011).unwrap(); + let transformations = TransformationRepository::from_builtin(); - let result_coords = transformation.transform(&initial_coords.adjust_epoch(&make_epoch(2010))); + let result_coords = transformations + .transform( + &initial_coords.clone().adjust_epoch(&make_epoch(2010)), + &ReferenceFrame::NAD83_2011, + ) + .unwrap(); assert_float_eq!(result_coords.position().x(), -2705104.349, abs <= 0.001); assert_float_eq!(result_coords.position().y(), -4262047.291, abs <= 0.001); assert_float_eq!(result_coords.position().z(), 3885381.579, abs <= 0.001); @@ -703,8 +750,9 @@ fn htdp_nad83_2011_adjust_epoch() { assert_eq!(result_coords.epoch(), make_epoch(2010)); assert_eq!(result_coords.reference_frame(), ReferenceFrame::NAD83_2011); - let result_coords = transformation - .transform(&initial_coords) + let result_coords = transformations + .transform(&initial_coords, &ReferenceFrame::NAD83_2011) + .unwrap() .adjust_epoch(&make_epoch(2010)); assert_float_eq!(result_coords.position().x(), -2705104.349, abs <= 0.001); assert_float_eq!(result_coords.position().y(), -4262047.291, abs <= 0.001); @@ -739,10 +787,11 @@ fn trx_nad83_csrs_fixed_date() { make_epoch(2010), ); - let transformation = - get_transformation(ReferenceFrame::ITRF2020, ReferenceFrame::NAD83_CSRS).unwrap(); + let transformations = TransformationRepository::from_builtin(); - let result_coords = transformation.transform(&initial_coords); + let result_coords = transformations + .transform(&initial_coords, &ReferenceFrame::NAD83_CSRS) + .unwrap(); assert_float_eq!(result_coords.position().x(), 1267459.462, abs <= 0.001); assert_float_eq!(result_coords.position().y(), -4294621.605, abs <= 0.001); assert_float_eq!(result_coords.position().z(), 4526843.224, abs <= 0.001); @@ -776,10 +825,14 @@ fn trx_nad83_csrs_adjust_epoch() { make_epoch(2020), ); - let transformation = - get_transformation(ReferenceFrame::ITRF2020, ReferenceFrame::NAD83_CSRS).unwrap(); + let transformations = TransformationRepository::from_builtin(); - let result_coords = transformation.transform(&initial_coords.adjust_epoch(&make_epoch(2010))); + let result_coords = transformations + .transform( + &initial_coords.clone().adjust_epoch(&make_epoch(2010)), + &ReferenceFrame::NAD83_CSRS, + ) + .unwrap(); assert_float_eq!(result_coords.position().x(), 1267459.620, abs <= 0.001); assert_float_eq!(result_coords.position().y(), -4294621.567, abs <= 0.001); assert_float_eq!(result_coords.position().z(), 4526843.177, abs <= 0.001); @@ -802,8 +855,9 @@ fn trx_nad83_csrs_adjust_epoch() { assert_eq!(result_coords.epoch(), make_epoch(2010)); assert_eq!(result_coords.reference_frame(), ReferenceFrame::NAD83_CSRS); - let result_coords = transformation - .transform(&initial_coords) + let result_coords = transformations + .transform(&initial_coords, &ReferenceFrame::NAD83_CSRS) + .unwrap() .adjust_epoch(&make_epoch(2010)); assert_float_eq!(result_coords.position().x(), 1267459.620, abs <= 0.001); assert_float_eq!(result_coords.position().y(), -4294621.567, abs <= 0.001); @@ -837,9 +891,11 @@ fn dref91_r2016() { None, UtcTime::from_parts(2023, 02, 22, 0, 0, 0.).to_gps_hardcoded(), ); - let transformation = - get_transformation(ReferenceFrame::ITRF2020, ReferenceFrame::DREF91_R2016).unwrap(); - let result_coords = transformation.transform(&initial_coords); + let transformations = TransformationRepository::from_builtin(); + + let result_coords = transformations + .transform(&initial_coords, &ReferenceFrame::DREF91_R2016) + .unwrap(); assert_float_eq!(result_coords.position().x(), 3842153.3718, abs <= 0.0001); assert_float_eq!(result_coords.position().y(), 563401.6528, abs <= 0.0001); assert_float_eq!(result_coords.position().z(), 5042888.2271, abs <= 0.0001);