use crate::frame_chain::{
apply_frame_transform, apply_frame_transform_inverse, find_frame_transform,
};
use oxiproj_core::{
epoch::Epoch,
plate_motion::{Plate, PlateMotionModel},
ProjError, ProjResult,
};
pub fn propagate_epoch(
x: f64,
y: f64,
z: f64,
from_epoch: &Epoch,
to_epoch: &Epoch,
model: PlateMotionModel,
plate: Plate,
) -> ProjResult<(f64, f64, f64)> {
match model.propagate(plate, x, y, z, from_epoch.year, to_epoch.year) {
Some(result) => Ok(result),
None => Err(ProjError::NoOperation),
}
}
pub fn plate_for_frame(frame_name: &str) -> Option<Plate> {
let lower = frame_name.to_ascii_lowercase();
if lower.contains("noam") || lower.contains("north_am") {
return Some(Plate::NorthAmerican);
}
if lower.contains("euras") {
return Some(Plate::Eurasian);
}
if lower.contains("afr") {
return Some(Plate::African);
}
if lower.contains("pacific") {
return Some(Plate::Pacific);
}
if lower.contains("antarcti") {
return Some(Plate::Antarctic);
}
if lower.contains("austral") {
return Some(Plate::Australian);
}
if lower.contains("south_am") {
return Some(Plate::SouthAmerican);
}
if lower.contains("sam") {
return Some(Plate::SouthAmerican);
}
if lower.contains("arabian") || lower.contains("arab") {
return Some(Plate::Arabian);
}
None
}
#[allow(clippy::too_many_arguments)]
pub fn epoch_path(
from_frame: &str,
to_frame: &str,
from_epoch: &Epoch,
to_epoch: &Epoch,
x: f64,
y: f64,
z: f64,
model: PlateMotionModel,
plate: Plate,
) -> ProjResult<(f64, f64, f64)> {
let (x1, y1, z1) = propagate_epoch(x, y, z, from_epoch, to_epoch, model, plate)?;
if from_frame.eq_ignore_ascii_case(to_frame) {
return Ok((x1, y1, z1));
}
if let Some(ft) = find_frame_transform(from_frame, to_frame) {
return apply_frame_transform(ft, x1, y1, z1, to_epoch.year)
.map_err(|_| ProjError::NoOperation);
}
if let Some(ft) = find_frame_transform(to_frame, from_frame) {
return apply_frame_transform_inverse(ft, x1, y1, z1, to_epoch.year)
.map_err(|_| ProjError::NoOperation);
}
Err(ProjError::NoOperation)
}
#[cfg(test)]
mod tests {
use super::*;
use oxiproj_core::epoch::Epoch;
use oxiproj_core::plate_motion::{Plate, PlateMotionModel};
#[test]
fn propagate_eurasian_plate_10_years() {
let (x, y, z) = (3_900_000.0_f64, 900_000.0_f64, 4_900_000.0_f64);
let from = Epoch::from_decimal_year(2015.0).unwrap();
let to = Epoch::from_decimal_year(2025.0).unwrap();
let (x2, y2, z2) = propagate_epoch(
x,
y,
z,
&from,
&to,
PlateMotionModel::Itrf2014,
Plate::Eurasian,
)
.unwrap();
let shift = ((x2 - x).powi(2) + (y2 - y).powi(2) + (z2 - z).powi(2)).sqrt();
assert!(shift > 0.05 && shift < 0.5, "shift={}", shift);
}
#[test]
fn plate_for_frame_lookups() {
assert_eq!(plate_for_frame("NOAM_2014"), Some(Plate::NorthAmerican));
assert_eq!(plate_for_frame("EURAS_plate"), Some(Plate::Eurasian));
assert_eq!(plate_for_frame("unknown_frame"), None);
}
#[test]
fn plate_for_frame_all_patterns() {
assert_eq!(plate_for_frame("noam_itrf"), Some(Plate::NorthAmerican));
assert_eq!(
plate_for_frame("north_am_plate"),
Some(Plate::NorthAmerican)
);
assert_eq!(plate_for_frame("AFR_2014"), Some(Plate::African));
assert_eq!(plate_for_frame("PACIFIC"), Some(Plate::Pacific));
assert_eq!(plate_for_frame("ANTARCTI_plate"), Some(Plate::Antarctic));
assert_eq!(plate_for_frame("AUSTRAL_zone"), Some(Plate::Australian));
assert_eq!(plate_for_frame("SOUTH_AM_2020"), Some(Plate::SouthAmerican));
assert_eq!(plate_for_frame("ARABIAN_plate"), Some(Plate::Arabian));
assert_eq!(plate_for_frame("ARAB_zone"), Some(Plate::Arabian));
assert_eq!(plate_for_frame("UNKNOWN"), None);
}
#[test]
fn propagate_returns_error_for_unknown_plate_in_model() {
let from = Epoch::from_decimal_year(2015.0).unwrap();
let to = Epoch::from_decimal_year(2025.0).unwrap();
let result = propagate_epoch(
1_000_000.0,
2_000_000.0,
3_000_000.0,
&from,
&to,
PlateMotionModel::NnrMorvel56,
Plate::Arabian,
);
assert!(result.is_err());
}
#[test]
fn epoch_path_same_frame_is_propagation_only() {
let from = Epoch::from_decimal_year(2020.0).unwrap();
let to = Epoch::from_decimal_year(2021.0).unwrap();
let (x, y, z) = (4_627_798.0_f64, 119_795.0_f64, 4_369_668.0_f64);
let result = epoch_path(
"ITRF2020",
"ITRF2020",
&from,
&to,
x,
y,
z,
PlateMotionModel::Itrf2020,
Plate::Eurasian,
);
assert!(
result.is_ok(),
"epoch_path same frame failed: {:?}",
result.err()
);
let (x2, y2, z2) = result.unwrap();
let direct = propagate_epoch(
x,
y,
z,
&from,
&to,
PlateMotionModel::Itrf2020,
Plate::Eurasian,
)
.unwrap();
assert!((x2 - direct.0).abs() < 1e-6, "x mismatch");
assert!((y2 - direct.1).abs() < 1e-6, "y mismatch");
assert!((z2 - direct.2).abs() < 1e-6, "z mismatch");
}
#[test]
fn epoch_path_itrf2020_to_itrf2014() {
let from = Epoch::from_decimal_year(2020.0).unwrap();
let to = Epoch::from_decimal_year(2020.0).unwrap();
let (x, y, z) = (4_627_798.0_f64, 119_795.0_f64, 4_369_668.0_f64);
let result = epoch_path(
"ITRF2020",
"ITRF2014",
&from,
&to,
x,
y,
z,
PlateMotionModel::Itrf2020,
Plate::Eurasian,
);
assert!(
result.is_ok(),
"epoch_path cross frame failed: {:?}",
result.err()
);
let (xo, yo, zo) = result.unwrap();
let dist = ((xo - x).powi(2) + (yo - y).powi(2) + (zo - z).powi(2)).sqrt();
assert!(dist < 1.0, "frame shift > 1m: {}", dist); assert!(dist > 0.0, "no shift at all");
}
}