use std::fs::File;
use std::io::{self, BufRead};
use std::path::PathBuf;
use std::sync::RwLock;
use super::astrotime;
use crate::utils::datadir;
use crate::utils::{download_file, download_if_not_exist};
use crate::{skerror, SKResult};
use once_cell::sync::OnceCell;
#[derive(Debug)]
#[allow(non_snake_case)]
struct EOPEntry {
mjd_utc: f64,
xp: f64,
yp: f64,
dut1: f64,
lod: f64,
dX: f64,
dY: f64,
}
fn load_eop_file_csv(filename: Option<PathBuf>) -> SKResult<Vec<EOPEntry>> {
let path: PathBuf = match filename {
Some(pb) => pb,
None => datadir().unwrap_or(PathBuf::from(".")).join("EOP-All.csv"),
};
download_if_not_exist(&path, Some("http://celestrak.org/SpaceData/"))?;
let file: File = File::open(&path)?;
io::BufReader::new(file)
.lines()
.skip(1)
.map(|rline| {
let line = rline.unwrap();
let lvals: Vec<&str> = line.split(",").collect();
if lvals.len() < 12 {
return skerror!("Invalid entry in EOP file");
}
Ok(EOPEntry {
mjd_utc: lvals[1].parse()?,
xp: lvals[2].parse()?,
yp: lvals[3].parse()?,
dut1: lvals[4].parse()?,
lod: lvals[5].parse()?,
dX: lvals[8].parse()?,
dY: lvals[9].parse()?,
})
})
.collect()
}
#[allow(dead_code)]
fn load_eop_file_legacy(filename: Option<PathBuf>) -> Vec<EOPEntry> {
let path: PathBuf = match filename {
Some(pb) => pb,
None => datadir()
.unwrap_or(PathBuf::from("."))
.join("finals2000A.all"),
};
if !path.is_file() {
panic!(
"Cannot open earth orientation parameters file: {}",
path.to_str().unwrap()
);
}
let file = match File::open(&path) {
Err(why) => panic!("Couldn't open {}: {}", path.display(), why),
Ok(file) => file,
};
let mut eopvec = Vec::<EOPEntry>::new();
for line in io::BufReader::new(file).lines() {
match &line.unwrap() {
v if v.len() < 100 => (),
v if !v.is_ascii() => (),
v if {
let c: String = v.chars().skip(16).take(1).collect();
c != "I" && c != "P"
} =>
{
()
}
v => {
let mjd_str: String = v.chars().skip(7).take(8).collect();
let xp_str: String = v.chars().skip(18).take(9).collect();
let yp_str: String = v.chars().skip(37).take(9).collect();
let dut1_str: String = v.chars().skip(58).take(10).collect();
let lod_str: String = v.chars().skip(49).take(7).collect();
let dx_str: String = v.chars().skip(97).take(9).collect();
let dy_str: String = v.chars().skip(116).take(9).collect();
eopvec.push(EOPEntry {
mjd_utc: {
match mjd_str.trim().parse() {
Ok(v) => v,
Err(_) => panic!("Could not extract mjd from file"),
}
},
xp: {
match xp_str.trim().parse() {
Ok(v) => v,
Err(_) => panic!("Could not extract x polar motion from file"),
}
},
yp: {
match yp_str.trim().parse() {
Ok(v) => v,
Err(_) => panic!("Could not extract y polar motion from file"),
}
},
dut1: {
match dut1_str.trim().parse() {
Ok(v) => v,
Err(_) => panic!("Could not extract dut1 from file"),
}
},
lod: lod_str.trim().parse().unwrap_or(0.0),
dX: {
match dx_str.trim().parse() {
Ok(v) => v,
Err(_) => 0.0,
}
},
dY: {
match dy_str.trim().parse() {
Ok(v) => v,
Err(_) => 0.0,
}
},
})
}
}
}
eopvec
}
fn eop_params_singleton() -> &'static RwLock<Vec<EOPEntry>> {
static INSTANCE: OnceCell<RwLock<Vec<EOPEntry>>> = OnceCell::new();
INSTANCE.get_or_init(|| RwLock::new(load_eop_file_csv(None).unwrap_or(Vec::<EOPEntry>::new())))
}
pub fn update() -> SKResult<()> {
let d = datadir()?;
if d.metadata()?.permissions().readonly() {
return skerror!(
r#"Data directory is read-only.
Try setting the environment variable SATKIT_DATA
to a writeable directory and re-starting or explicitly set
data directory"#
);
}
let url = "http://celestrak.org/SpaceData/EOP-All.csv";
download_file(url, &d, true)?;
*eop_params_singleton().write().unwrap() = load_eop_file_csv(None).unwrap();
Ok(())
}
pub fn eop_from_mjd_utc(mjd_utc: f64) -> Option<[f64; 6]> {
let eop = eop_params_singleton().read().unwrap();
let idx = eop.iter().position(|x| x.mjd_utc > mjd_utc);
match idx {
None => None,
Some(v) => {
if v == 0 as usize {
return None;
}
let g1: f64 = (mjd_utc - eop[v - 1].mjd_utc) / (eop[v].mjd_utc - eop[v - 1].mjd_utc);
let g0: f64 = 1.0 - g1;
let v1: &EOPEntry = &eop[v];
let v0: &EOPEntry = &eop[v - 1];
Some([
g0 * v0.dut1 + g1 * v1.dut1,
g0 * v0.xp + g1 * v1.xp,
g0 * v0.yp + g1 * v1.yp,
g0 * v0.lod + g1 * v1.lod,
g0 * v0.dX + g1 * v1.dX,
g0 * v0.dY + g1 * v1.dY,
])
}
}
}
#[inline]
pub fn get(tm: &astrotime::AstroTime) -> Option<[f64; 6]> {
eop_from_mjd_utc(tm.to_mjd(astrotime::Scale::UTC))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn loaded() {
assert_eq!(
eop_params_singleton().read().unwrap()[0].mjd_utc >= 0.0,
true
);
}
#[test]
fn checkval() {
let v = eop_from_mjd_utc(59464.00).unwrap();
const TRUTH: [f64; 4] = [-0.1145667, 0.241155, 0.317274, -0.0002255];
for it in v.iter().zip(TRUTH.iter()) {
let (a, b) = it;
assert!(((a - b) / b).abs() < 1.0e-3);
}
}
#[test]
fn checkinterp() {
let mjd0: f64 = 57909.00;
const TRUTH0: [f64; 4] = [0.3754421, 0.102693, 0.458455, 0.0011699];
const TRUTH1: [f64; 4] = [0.3743358, 0.104031, 0.458373, 0.0010383];
for x in 0..101 {
let dt: f64 = x as f64 / 100.0;
let vt = eop_from_mjd_utc(mjd0 + dt).unwrap();
let g0: f64 = 1.0 - dt;
let g1: f64 = dt;
for it in vt.iter().zip(TRUTH0.iter().zip(TRUTH1.iter())) {
let (v, (v0, v1)) = it;
let vtest: f64 = g0 * v0 + g1 * v1;
assert!(((v - vtest) / v).abs() < 1.0e-5);
}
}
}
}