celestial_coords/eop/
mod.rs1pub mod bundled;
2pub mod interpolate;
3pub mod parse;
4pub mod record;
5
6pub use record::{EopParameters, EopRecord};
7
8use interpolate::{EopInterpolator, InterpolationMethod};
9
10use crate::{CoordError, CoordResult};
11use std::path::Path;
12
13pub struct EopProvider {
14 interpolator: EopInterpolator,
15}
16
17impl EopProvider {
18 pub fn bundled() -> CoordResult<Self> {
19 let records = bundled::load_bundled_combined()?;
20 Self::from_records(records)
21 }
22
23 pub fn bundled_c04() -> CoordResult<Self> {
24 let records = bundled::load_bundled_c04()?;
25 Self::from_records(records)
26 }
27
28 pub fn from_records(records: Vec<EopRecord>) -> CoordResult<Self> {
29 if records.is_empty() {
30 return Err(CoordError::data_unavailable(
31 "Cannot create EopProvider with empty records",
32 ));
33 }
34 Ok(Self {
35 interpolator: EopInterpolator::new(records),
36 })
37 }
38
39 pub fn with_interpolation(mut self, method: InterpolationMethod) -> Self {
40 self.interpolator = self.interpolator.with_method(method);
41 self
42 }
43
44 pub fn get(&self, mjd: f64) -> CoordResult<EopParameters> {
45 self.interpolator.get(mjd)
46 }
47
48 pub fn time_span(&self) -> Option<(f64, f64)> {
49 self.interpolator.time_span()
50 }
51
52 pub fn record_count(&self) -> usize {
53 self.interpolator.record_count()
54 }
55
56 pub fn from_finals_str(content: &str) -> CoordResult<Self> {
57 let records = parse::parse_finals(content)?;
58 Self::from_records(records)
59 }
60
61 pub fn from_finals_file(path: impl AsRef<Path>) -> CoordResult<Self> {
62 let content = std::fs::read_to_string(path.as_ref()).map_err(|e| {
63 CoordError::external_library("reading finals2000A file", &e.to_string())
64 })?;
65 Self::from_finals_str(&content)
66 }
67
68 pub fn bundled_with_update(path: impl AsRef<Path>) -> CoordResult<Self> {
69 let mut provider = Self::bundled()?;
70 let update_content = std::fs::read_to_string(path.as_ref()).map_err(|e| {
71 CoordError::external_library("reading finals2000A update file", &e.to_string())
72 })?;
73 let update_records = parse::parse_finals(&update_content)?;
74 provider.interpolator.extend(update_records);
75 Ok(provider)
76 }
77}
78
79#[cfg(test)]
80mod tests {
81 use super::*;
82
83 #[test]
84 fn test_bundled_provider() {
85 let provider = EopProvider::bundled().unwrap();
86 assert!(provider.record_count() > 0);
87 assert!(provider.time_span().is_some());
88 }
89
90 #[test]
91 fn test_bundled_lookup() {
92 let provider = EopProvider::bundled().unwrap();
93 let params = provider.get(59945.0).unwrap();
94 assert_eq!(params.mjd, 59945.0);
95 assert!(params.x_p.abs() < 1.0);
96 assert!(params.y_p.abs() < 1.0);
97 assert!(params.ut1_utc.abs() < 1.0);
98 }
99
100 #[test]
101 fn test_from_records() {
102 let records = vec![
103 EopRecord::new(60000.0, 0.1, 0.2, 0.01, 0.001).unwrap(),
104 EopRecord::new(60001.0, 0.101, 0.202, 0.011, 0.001).unwrap(),
105 ];
106 let provider = EopProvider::from_records(records).unwrap();
107 let params = provider.get(60000.5).unwrap();
108 assert!((params.x_p - 0.1005).abs() < 1e-7);
109 }
110
111 #[test]
112 fn test_empty_records_rejected() {
113 let result = EopProvider::from_records(vec![]);
114 assert!(result.is_err());
115 }
116
117 #[test]
118 fn test_out_of_range() {
119 let provider = EopProvider::bundled().unwrap();
120 assert!(provider.get(70000.0).is_err());
121 }
122
123 #[test]
124 fn test_immutable_get() {
125 let provider = EopProvider::bundled().unwrap();
126 let _p1 = provider.get(59945.0).unwrap();
127 let _p2 = provider.get(59945.0).unwrap();
128 }
129}