#![allow(dead_code)]
use crate::error::EclipseError;
use pleiades_apparent::{apparent_sun_position, LIGHT_TIME_DAYS_PER_AU};
use pleiades_backend::{EphemerisBackend, EphemerisRequest};
use pleiades_types::{
Apparentness, CelestialBody, CoordinateFrame, EclipticCoordinates, Instant, JulianDay,
Latitude, Longitude, TimeScale, ZodiacMode,
};
#[derive(Clone, Copy, Debug)]
pub(crate) struct SunMoonSample {
pub sun_longitude_deg: f64,
pub sun_latitude_deg: f64,
pub sun_distance_au: f64,
pub moon_longitude_deg: f64,
pub moon_latitude_deg: f64,
pub moon_distance_au: f64,
}
fn request(body: CelestialBody, julian_day: f64) -> EphemerisRequest {
EphemerisRequest {
body,
instant: Instant::new(JulianDay::from_days(julian_day), TimeScale::Tdb),
observer: None,
frame: CoordinateFrame::Ecliptic,
zodiac_mode: ZodiacMode::Tropical,
apparent: Apparentness::Mean,
}
}
fn read<B: EphemerisBackend>(
backend: &B,
body: CelestialBody,
body_label: &'static str,
julian_day: f64,
) -> Result<(f64, f64, f64), EclipseError> {
let result = backend
.position(&request(body, julian_day))
.map_err(|e| EclipseError::Backend(e.to_string()))?;
let ecliptic = result.ecliptic.ok_or(EclipseError::MissingCoordinates {
body_label,
julian_day,
})?;
let distance = ecliptic
.distance_au
.ok_or(EclipseError::MissingCoordinates {
body_label,
julian_day,
})?;
Ok((
ecliptic.longitude.degrees(),
ecliptic.latitude.degrees(),
distance,
))
}
fn read_retarded<B: EphemerisBackend>(
backend: &B,
body: CelestialBody,
body_label: &'static str,
julian_day: f64,
) -> Result<(f64, f64, f64), EclipseError> {
let (_, _, dist) = read(backend, body.clone(), body_label, julian_day)?;
let light_time_days = dist * LIGHT_TIME_DAYS_PER_AU;
read(backend, body, body_label, julian_day - light_time_days)
}
pub(crate) fn sample_sun_moon<B: EphemerisBackend>(
backend: &B,
julian_day: f64,
) -> Result<SunMoonSample, EclipseError> {
let (sun_longitude_deg, sun_latitude_deg, sun_distance_au) =
read_retarded(backend, CelestialBody::Sun, "Sun", julian_day)?;
let (moon_longitude_deg, moon_latitude_deg, moon_distance_au) =
read_retarded(backend, CelestialBody::Moon, "Moon", julian_day)?;
Ok(SunMoonSample {
sun_longitude_deg,
sun_latitude_deg,
sun_distance_au,
moon_longitude_deg,
moon_latitude_deg,
moon_distance_au,
})
}
pub(crate) fn elongation_deg(sample: &SunMoonSample) -> f64 {
let mut d = sample.moon_longitude_deg - sample.sun_longitude_deg;
d = ((d + 180.0).rem_euclid(360.0)) - 180.0;
d
}
pub(crate) fn apparent_sun_longitude_deg<B: EphemerisBackend>(
backend: &B,
julian_day: f64,
) -> Result<f64, EclipseError> {
let (sun_lon_j2000, sun_lat_j2000, sun_dist_au) =
read(backend, CelestialBody::Sun, "Sun", julian_day)?;
let sun_j2000 = EclipticCoordinates::new(
Longitude::from_degrees(sun_lon_j2000),
Latitude::from_degrees(sun_lat_j2000),
Some(sun_dist_au),
);
let instant = Instant::new(JulianDay::from_days(julian_day), TimeScale::Tdb);
let apparent = apparent_sun_position(instant, sun_j2000)
.map_err(|e| EclipseError::Backend(format!("Sun apparent place failed: {e}")))?;
Ok(apparent.ecliptic.longitude.degrees())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::error::EclipseError;
use pleiades_backend::test_backend::LinearSunMoon;
#[test]
fn elongation_reflects_light_time_at_geometric_new_moon() {
let backend = LinearSunMoon::new_moon_at(2_451_550.0);
let sample = sample_sun_moon(&backend, 2_451_550.0).unwrap();
let e = elongation_deg(&sample);
assert!((e - 0.005_498).abs() < 1e-4, "apparent elongation {e}");
}
#[test]
fn missing_coordinates_fail_closed() {
let backend = LinearSunMoon::empty();
let err = sample_sun_moon(&backend, 2_451_550.0).unwrap_err();
assert!(matches!(err, EclipseError::MissingCoordinates { .. }));
}
}