use crate::{Location, julian_date};
use crate::error::{Result, validate_ra, validate_dec};
use chrono::{DateTime, Utc};
const EARTH_RADIUS_KM: f64 = 6378.137;
const EARTH_FLATTENING: f64 = 1.0 / 298.257223563;
const AU_KM: f64 = 149597870.7;
pub fn geocentric_distance(location: &Location) -> f64 {
let lat_rad = location.latitude_deg.to_radians();
let alt_km = location.altitude_m / 1000.0;
let u = ((1.0 - EARTH_FLATTENING) * lat_rad.tan()).atan();
let rho_cos_phi = u.cos() + (alt_km / EARTH_RADIUS_KM) * lat_rad.cos();
let rho_sin_phi = (1.0 - EARTH_FLATTENING).powi(2) * u.sin() + (alt_km / EARTH_RADIUS_KM) * lat_rad.sin();
(rho_cos_phi.powi(2) + rho_sin_phi.powi(2)).sqrt()
}
pub fn diurnal_parallax(
ra: f64,
dec: f64,
distance_au: f64,
datetime: DateTime<Utc>,
location: &Location,
) -> Result<(f64, f64)> {
validate_ra(ra)?;
validate_dec(dec)?;
if distance_au <= 0.0 {
return Err(crate::error::AstroError::OutOfRange {
parameter: "distance_au",
value: distance_au,
min: f64::MIN_POSITIVE,
max: f64::MAX,
});
}
let lst_hours = location.local_sidereal_time(datetime);
let lst_deg = lst_hours * 15.0;
let ha = lst_deg - ra;
let ha_rad = ha.to_radians();
let dec_rad = dec.to_radians();
let lat_rad = location.latitude_deg.to_radians();
let u = ((1.0 - EARTH_FLATTENING) * lat_rad.tan()).atan();
let rho_cos = u.cos() + (location.altitude_m / 1000.0 / EARTH_RADIUS_KM) * lat_rad.cos();
let rho_sin = (1.0 - EARTH_FLATTENING).powi(2) * u.sin() +
(location.altitude_m / 1000.0 / EARTH_RADIUS_KM) * lat_rad.sin();
let parallax_as = 8.794 / (distance_au * AU_KM / EARTH_RADIUS_KM);
let parallax_rad = (parallax_as / 3600.0).to_radians();
let cos_dec = dec_rad.cos();
let sin_dec = dec_rad.sin();
let cos_ha = ha_rad.cos();
let sin_ha = ha_rad.sin();
let p_ra = -parallax_rad * rho_cos * sin_ha / cos_dec;
let p_dec = -parallax_rad * (rho_sin * cos_dec - rho_cos * cos_ha * sin_dec);
let ra_corrected = ra + p_ra.to_degrees();
let dec_corrected = dec + p_dec.to_degrees();
let ra_normalized = if ra_corrected < 0.0 {
ra_corrected + 360.0
} else if ra_corrected >= 360.0 {
ra_corrected - 360.0
} else {
ra_corrected
};
Ok((ra_normalized, dec_corrected))
}
pub fn annual_parallax(
ra: f64,
dec: f64,
parallax_mas: f64,
datetime: DateTime<Utc>,
) -> Result<(f64, f64)> {
validate_ra(ra)?;
validate_dec(dec)?;
if parallax_mas <= 0.0 {
return Err(crate::error::AstroError::OutOfRange {
parameter: "parallax_mas",
value: parallax_mas,
min: f64::MIN_POSITIVE,
max: f64::MAX,
});
}
let jd = julian_date(datetime);
let t = (jd - 2451545.0) / 36525.0;
let l = 280.46646 + 36000.76983 * t + 0.0003032 * t * t;
let m = 357.52911 + 35999.05029 * t - 0.0001537 * t * t;
let m_rad = m.to_radians();
let c = (1.914602 - 0.004817 * t - 0.000014 * t * t) * m_rad.sin()
+ (0.019993 - 0.000101 * t) * (2.0 * m_rad).sin()
+ 0.000289 * (3.0 * m_rad).sin();
let sun_lon = l + c;
let sun_lon_rad = sun_lon.to_radians();
let parallax_rad = (parallax_mas / 1000.0 / 3600.0).to_radians();
let ra_rad = ra.to_radians();
let dec_rad = dec.to_radians();
let cos_dec = dec_rad.cos();
let sin_dec = dec_rad.sin();
let delta_ra = parallax_rad * (sun_lon_rad.cos() * ra_rad.sin() - sun_lon_rad.sin() * ra_rad.cos()) / cos_dec;
let delta_dec = parallax_rad * (sun_lon_rad.sin() * sin_dec * ra_rad.sin() + sun_lon_rad.cos() * sin_dec * ra_rad.cos());
Ok((ra + delta_ra.to_degrees(), dec + delta_dec.to_degrees()))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Location;
use chrono::{TimeZone, Utc};
#[test]
fn test_geocentric_distance() {
let loc = Location {
latitude_deg: 0.0,
longitude_deg: 0.0,
altitude_m: 0.0,
};
let dist = geocentric_distance(&loc);
assert!((dist - 1.0).abs() < 0.001);
let loc_high = Location {
latitude_deg: 0.0,
longitude_deg: 0.0,
altitude_m: 8848.0, };
let dist_high = geocentric_distance(&loc_high);
assert!(dist_high > 1.001);
}
#[test]
fn test_diurnal_parallax_moon() {
let dt = Utc.with_ymd_and_hms(2024, 8, 4, 22, 0, 0).unwrap();
let location = Location {
latitude_deg: 40.0,
longitude_deg: -74.0,
altitude_m: 0.0,
};
let (ra_topo, dec_topo) = diurnal_parallax(45.0, 30.0, 0.00257, dt, &location).unwrap();
assert!((ra_topo - 45.0).abs() < 1.0); assert!((dec_topo - 30.0).abs() < 1.0);
}
#[test]
fn test_diurnal_parallax_distant() {
let dt = Utc.with_ymd_and_hms(2024, 8, 4, 22, 0, 0).unwrap();
let location = Location {
latitude_deg: 40.0,
longitude_deg: -74.0,
altitude_m: 0.0,
};
let (ra_topo, dec_topo) = diurnal_parallax(45.0, 30.0, 1000.0, dt, &location).unwrap();
assert!((ra_topo - 45.0).abs() < 0.001);
assert!((dec_topo - 30.0).abs() < 0.001);
}
#[test]
fn test_annual_parallax() {
let dt = Utc.with_ymd_and_hms(2024, 8, 4, 0, 0, 0).unwrap();
let (ra_corrected, dec_corrected) = annual_parallax(217.42894, -62.67948, 768.5, dt).unwrap();
assert!((ra_corrected - 217.42894).abs() < 0.001);
assert!((dec_corrected - (-62.67948)).abs() < 0.001);
}
}