use chrono::{DateTime, Utc};
use crate::error::{Result, validate_ra, validate_dec};
pub fn get_precession_angles(jd: f64) -> (f64, f64, f64) {
let (_eps0, _psia, _oma, _bpa, _bqa, _pia, _bpia,
_epsa, _chia, za, zetaa, thetaa, _pa, _gam, _phi, _psi) =
erfars::precnutpolar::P06e(jd, 0.0);
(zetaa.to_degrees(), za.to_degrees(), thetaa.to_degrees())
}
pub fn get_precession_matrix(jd: f64) -> [[f64; 3]; 3] {
let mut rbp = [0.0; 9];
erfars::precnutpolar::Pmat06(jd, 0.0, &mut rbp);
[
[rbp[0], rbp[1], rbp[2]],
[rbp[3], rbp[4], rbp[5]],
[rbp[6], rbp[7], rbp[8]],
]
}
pub fn precess_from_j2000(ra_j2000: f64, dec_j2000: f64, datetime: DateTime<Utc>) -> Result<(f64, f64)> {
validate_ra(ra_j2000)?;
validate_dec(dec_j2000)?;
let jd = crate::julian_date(datetime);
let ra_rad = ra_j2000.to_radians();
let dec_rad = dec_j2000.to_radians();
let mut rbp = [0.0; 9];
erfars::precnutpolar::Pmat06(jd, 0.0, &mut rbp);
let cos_ra = ra_rad.cos();
let sin_ra = ra_rad.sin();
let cos_dec = dec_rad.cos();
let sin_dec = dec_rad.sin();
let p = [
cos_dec * cos_ra,
cos_dec * sin_ra,
sin_dec,
];
let p_new = [
rbp[0] * p[0] + rbp[1] * p[1] + rbp[2] * p[2],
rbp[3] * p[0] + rbp[4] * p[1] + rbp[5] * p[2],
rbp[6] * p[0] + rbp[7] * p[1] + rbp[8] * p[2],
];
let ra_new = p_new[1].atan2(p_new[0]);
let dec_new = p_new[2].asin();
let mut ra_deg = ra_new.to_degrees();
if ra_deg < 0.0 {
ra_deg += 360.0;
} else if ra_deg >= 360.0 {
ra_deg -= 360.0;
}
Ok((ra_deg, dec_new.to_degrees()))
}
pub fn precess_to_j2000(ra: f64, dec: f64, datetime: DateTime<Utc>) -> Result<(f64, f64)> {
validate_ra(ra)?;
validate_dec(dec)?;
let jd = crate::julian_date(datetime);
let ra_rad = ra.to_radians();
let dec_rad = dec.to_radians();
let mut rbp = [0.0; 9];
erfars::precnutpolar::Pmat06(jd, 0.0, &mut rbp);
let rbp_t = [
rbp[0], rbp[3], rbp[6],
rbp[1], rbp[4], rbp[7],
rbp[2], rbp[5], rbp[8],
];
let cos_ra = ra_rad.cos();
let sin_ra = ra_rad.sin();
let cos_dec = dec_rad.cos();
let sin_dec = dec_rad.sin();
let p = [
cos_dec * cos_ra,
cos_dec * sin_ra,
sin_dec,
];
let p_j2000 = [
rbp_t[0] * p[0] + rbp_t[1] * p[1] + rbp_t[2] * p[2],
rbp_t[3] * p[0] + rbp_t[4] * p[1] + rbp_t[5] * p[2],
rbp_t[6] * p[0] + rbp_t[7] * p[1] + rbp_t[8] * p[2],
];
let ra_j2000 = p_j2000[1].atan2(p_j2000[0]);
let dec_j2000 = p_j2000[2].asin();
let mut ra_deg = ra_j2000.to_degrees();
if ra_deg < 0.0 {
ra_deg += 360.0;
} else if ra_deg >= 360.0 {
ra_deg -= 360.0;
}
Ok((ra_deg, dec_j2000.to_degrees()))
}
#[cfg(test)]
mod tests {
use super::*;
use chrono::{TimeZone, Utc};
#[test]
fn test_get_precession_angles() {
let (zeta, z, theta) = get_precession_angles(2451545.0);
assert!((zeta - 0.0007362625).abs() < 1e-9, "zeta at J2000.0: {}", zeta);
assert!((z - (-0.0007362625)).abs() < 1e-9, "z at J2000.0: {}", z);
assert!(theta.abs() < 1e-10, "theta at J2000.0: {}", theta);
let (zeta, z, theta) = get_precession_angles(2469807.5);
assert!((zeta - 0.3210).abs() < 0.0001, "zeta: {}", zeta);
assert!((z - 0.3196).abs() < 0.0001, "z: {}", z);
assert!((theta - 0.2783).abs() < 0.0001, "theta: {}", theta);
}
#[test]
fn test_get_precession_matrix() {
let matrix = get_precession_matrix(2451545.0);
assert!((matrix[0][0] - 1.0).abs() < 1e-7);
assert!((matrix[1][1] - 1.0).abs() < 1e-7);
assert!((matrix[2][2] - 1.0).abs() < 1e-7);
assert!(matrix[0][1].abs() < 1e-7);
assert!(matrix[0][2].abs() < 1e-7);
let det = matrix[0][0] * (matrix[1][1] * matrix[2][2] - matrix[1][2] * matrix[2][1])
- matrix[0][1] * (matrix[1][0] * matrix[2][2] - matrix[1][2] * matrix[2][0])
+ matrix[0][2] * (matrix[1][0] * matrix[2][1] - matrix[1][1] * matrix[2][0]);
assert!((det - 1.0).abs() < 1e-10, "Determinant should be 1, got {}", det);
}
#[test]
fn test_precess_from_j2000() {
let dt = Utc.with_ymd_and_hms(2050, 1, 1, 0, 0, 0).unwrap();
let (ra, dec) = precess_from_j2000(37.95456067, 89.26410897, dt).unwrap();
assert!(ra > 50.0 && ra < 60.0); assert!((dec - 89.45).abs() < 0.01);
}
#[test]
fn test_precession_roundtrip() {
let dt = Utc.with_ymd_and_hms(2025, 6, 15, 12, 0, 0).unwrap();
let ra_original = 83.633;
let dec_original = 22.0145;
let (ra_precessed, dec_precessed) = precess_from_j2000(ra_original, dec_original, dt).unwrap();
let (ra_back, dec_back) = precess_to_j2000(ra_precessed, dec_precessed, dt).unwrap();
assert!((ra_back - ra_original).abs() < 0.001); assert!((dec_back - dec_original).abs() < 0.001);
}
#[test]
fn test_precess_vega() {
let dt = Utc.with_ymd_and_hms(2025, 1, 1, 0, 0, 0).unwrap();
let (ra, dec) = precess_from_j2000(279.23473479, 38.78368896, dt).unwrap();
assert!((ra - 279.23473479).abs() < 0.5); assert!((dec - 38.78368896).abs() < 0.05); }
}