use crate::coords::{deg_to_rad, normalize_degrees, rad_to_deg};
const C_AU_DAY: f64 = 173.144_632_72;
const ABERRATION_CONSTANT: f64 = 20.495_52;
pub fn light_time(distance_au: f64) -> f64 {
distance_au / C_AU_DAY
}
pub fn light_time_correction<F, G>(jd: f64, planet_at_jd: F, earth_at_jd: G) -> (f64, f64, f64, f64)
where
F: Fn(f64) -> (f64, f64, f64),
G: Fn(f64) -> (f64, f64, f64),
{
let (e_lon, _e_lat, e_r) = earth_at_jd(jd);
let (mut p_lon, mut p_lat, mut p_r) = planet_at_jd(jd);
let mut dist = geocentric_distance(p_lon, p_lat, p_r, e_lon, e_r);
let mut tau = light_time(dist);
for _ in 0..3 {
let (lon, lat, r) = planet_at_jd(jd - tau);
p_lon = lon;
p_lat = lat;
p_r = r;
dist = geocentric_distance(p_lon, p_lat, p_r, e_lon, e_r);
tau = light_time(dist);
}
let p_lon_r = deg_to_rad(p_lon);
let p_lat_r = deg_to_rad(p_lat);
let e_lon_r = deg_to_rad(e_lon);
let x = p_r * p_lat_r.cos() * p_lon_r.cos() - e_r * e_lon_r.cos();
let y = p_r * p_lat_r.cos() * p_lon_r.sin() - e_r * e_lon_r.sin();
let z = p_r * p_lat_r.sin();
let geo_lon = normalize_degrees(rad_to_deg(y.atan2(x)));
let geo_lat = rad_to_deg(z.atan2((x * x + y * y).sqrt()));
(geo_lon, geo_lat, dist, tau)
}
fn geocentric_distance(p_lon: f64, p_lat: f64, p_r: f64, e_lon: f64, e_r: f64) -> f64 {
let p_lon_r = deg_to_rad(p_lon);
let p_lat_r = deg_to_rad(p_lat);
let e_lon_r = deg_to_rad(e_lon);
let x = p_r * p_lat_r.cos() * p_lon_r.cos() - e_r * e_lon_r.cos();
let y = p_r * p_lat_r.cos() * p_lon_r.sin() - e_r * e_lon_r.sin();
let z = p_r * p_lat_r.sin();
(x * x + y * y + z * z).sqrt()
}
pub fn annual_aberration(sun_lon: f64, obj_lon: f64, obj_lat: f64, obliquity: f64) -> (f64, f64) {
let sun_r = deg_to_rad(sun_lon);
let lon_r = deg_to_rad(obj_lon);
let lat_r = deg_to_rad(obj_lat);
let _eps_r = deg_to_rad(obliquity); let kappa = ABERRATION_CONSTANT / 3600.0;
let cos_lat = lat_r.cos();
let safe_cos_lat = if cos_lat.abs() < 1e-10 {
1e-10_f64.copysign(cos_lat)
} else {
cos_lat
};
let delta_lon = (-kappa * (sun_r - lon_r).cos() / safe_cos_lat)
+ (kappa * 0.016_708_634 * (deg_to_rad(102.937_35) - lon_r).cos() / safe_cos_lat);
let delta_lat = -kappa
* lat_r.sin()
* ((sun_r - lon_r).sin() - 0.016_708_634 * (deg_to_rad(102.937_35) - lon_r).sin());
(delta_lon, delta_lat)
}
pub fn apply_aberration(geo_lon: f64, geo_lat: f64, sun_lon: f64, obliquity: f64) -> (f64, f64) {
let (dlon, dlat) = annual_aberration(sun_lon, geo_lon, geo_lat, obliquity);
(normalize_degrees(geo_lon + dlon), geo_lat + dlat)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn light_time_sun() {
let lt = light_time(1.0);
assert!((lt - 0.005_776).abs() < 0.001, "got {lt}");
}
#[test]
fn light_time_jupiter() {
let lt = light_time(5.0);
assert!(
(lt * 86400.0 - 2497.0).abs() < 10.0,
"got {} seconds",
lt * 86400.0
);
}
#[test]
fn aberration_magnitude() {
let mut max_abs = 0.0_f64;
for obj_lon in (0..360).step_by(10) {
let (dlon, _) = annual_aberration(280.0, obj_lon as f64, 0.0, 23.44);
max_abs = max_abs.max(dlon.abs());
}
assert!(max_abs > 0.004 && max_abs < 0.007, "max_abs = {max_abs}°");
}
#[test]
fn aberration_zero_latitude() {
let (_dlon, dlat) = annual_aberration(280.0, 100.0, 0.0, 23.44);
assert!(dlat.abs() < 0.001, "dlat = {dlat}°");
}
#[test]
fn apply_aberration_roundtrip() {
let lon = 100.0;
let lat = 5.0;
let (app_lon, app_lat) = apply_aberration(lon, lat, 280.0, 23.44);
assert!((app_lon - lon).abs() < 0.01);
assert!((app_lat - lat).abs() < 0.01);
}
#[test]
fn light_time_correction_basic() {
let planet_fn = |_jd: f64| (100.0_f64, 2.0_f64, 1.5_f64);
let earth_fn = |_jd: f64| (280.0_f64, 0.0_f64, 1.0_f64);
let (lon, lat, dist, tau) = light_time_correction(2_451_545.0, planet_fn, earth_fn);
assert!((0.0..360.0).contains(&lon));
assert!(lat.abs() < 90.0);
assert!(dist > 0.0);
assert!(tau > 0.0);
}
}