use super::FormatError;
use qtty::angular::{Arcseconds, MilliArcseconds};
use qtty::time::Seconds;
use qtty::Day;
use std::io::{BufRead, BufReader, Read};
use tempoch::{ModifiedJulianDate, UTC};
#[derive(Debug, Clone, Copy)]
pub struct EopValues {
pub dut1: Seconds,
pub lod: Seconds,
pub xp: Arcseconds,
pub yp: Arcseconds,
pub dx: MilliArcseconds,
pub dy: MilliArcseconds,
}
#[derive(Debug, Clone, Copy)]
pub struct EopRecord {
pub mjd: ModifiedJulianDate<UTC>,
pub eop: EopValues,
}
pub fn read_eop_c04<R: Read>(rdr: R) -> Result<Vec<EopRecord>, FormatError> {
let br = BufReader::new(rdr);
let mut out = Vec::new();
for line in br.lines() {
let line = line?;
let trimmed = line.trim_start();
if trimmed.is_empty() || trimmed.starts_with('#') {
continue;
}
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.len() < 10 {
continue;
}
if parts[0].parse::<i32>().is_err()
|| parts[1].parse::<i32>().is_err()
|| parts[2].parse::<i32>().is_err()
{
continue;
}
let mjd_raw: f64 = match parts[3].parse() {
Ok(v) => v,
Err(_) => continue,
};
let mjd = match ModifiedJulianDate::<UTC>::try_new(Day::new(mjd_raw)) {
Ok(m) => m,
Err(_) => continue,
};
let x = parts[4].parse().unwrap_or(0.0);
let y = parts[5].parse().unwrap_or(0.0);
let ut1 = parts[6].parse().unwrap_or(0.0);
let lod = parts[7].parse().unwrap_or(0.0);
let dpsi = parts[8].parse().unwrap_or(0.0);
let deps = parts[9].parse().unwrap_or(0.0);
out.push(EopRecord {
mjd,
eop: EopValues {
xp: Arcseconds::new(x),
yp: Arcseconds::new(y),
dut1: Seconds::new(ut1),
lod: Seconds::new(lod),
dx: MilliArcseconds::new(dpsi * 1000.0),
dy: MilliArcseconds::new(deps * 1000.0),
},
});
}
Ok(out)
}
pub fn interpolate(records: &[EopRecord], mjd: ModifiedJulianDate<UTC>) -> Option<EopRecord> {
if records.is_empty() {
return None;
}
if mjd <= records[0].mjd {
return Some(records[0]);
}
if mjd >= records[records.len() - 1].mjd {
return Some(records[records.len() - 1]);
}
let mut lo = 0usize;
let mut hi = records.len() - 1;
while hi - lo > 1 {
let mid = (lo + hi) / 2;
if records[mid].mjd <= mjd {
lo = mid;
} else {
hi = mid;
}
}
let a = records[lo];
let b = records[hi];
let a_val = a.mjd.raw().value();
let b_val = b.mjd.raw().value();
let t_val = mjd.raw().value();
let f = (t_val - a_val) / (b_val - a_val);
Some(EopRecord {
mjd,
eop: EopValues {
xp: Arcseconds::new(a.eop.xp.value() + f * (b.eop.xp.value() - a.eop.xp.value())),
yp: Arcseconds::new(a.eop.yp.value() + f * (b.eop.yp.value() - a.eop.yp.value())),
dut1: Seconds::new(a.eop.dut1.value() + f * (b.eop.dut1.value() - a.eop.dut1.value())),
lod: Seconds::new(a.eop.lod.value() + f * (b.eop.lod.value() - a.eop.lod.value())),
dx: MilliArcseconds::new(a.eop.dx.value() + f * (b.eop.dx.value() - a.eop.dx.value())),
dy: MilliArcseconds::new(a.eop.dy.value() + f * (b.eop.dy.value() - a.eop.dy.value())),
},
})
}
#[cfg(test)]
mod tests {
use super::*;
const SAMPLE: &str = "\
# IERS C04 sample
2024 1 1 60310 0.123456 0.234567 0.012345 0.001234 0.0001 0.0002
2024 1 2 60311 0.124000 0.235000 0.012000 0.001200 0.0001 0.0002
";
#[test]
fn parses_two_records() {
let r = read_eop_c04(SAMPLE.as_bytes()).unwrap();
assert_eq!(r.len(), 2);
assert!((r[0].mjd.raw().value() - 60310.0).abs() < 1e-9);
}
#[test]
fn interpolates_midpoint() {
let r = read_eop_c04(SAMPLE.as_bytes()).unwrap();
let mid = interpolate(
&r,
ModifiedJulianDate::<UTC>::try_new(Day::new(60310.5)).unwrap(),
)
.unwrap();
let expect = (0.123456 + 0.124000) / 2.0;
assert!((mid.eop.xp.value() - expect).abs() < 1e-9);
}
}