use crate::julian_date;
use chrono::{DateTime, Utc};
pub fn moon_position(datetime: DateTime<Utc>) -> (f64, f64) {
let jd = julian_date(datetime);
use crate::time_scales::utc_to_tt_jd;
let tt = utc_to_tt_jd(jd);
let pv = erfars::ephemerides::Moon98(tt, 0.0);
let x = pv[0];
let y = pv[1];
let z = pv[2];
let eps_rad = erfars::precnutpolar::Obl06(tt, 0.0);
let cos_eps = eps_rad.cos();
let sin_eps = eps_rad.sin();
let x_ecl = x;
let y_ecl = cos_eps * y + sin_eps * z;
let z_ecl = -sin_eps * y + cos_eps * z;
let lon_rad = y_ecl.atan2(x_ecl);
let lat_rad = z_ecl.atan2((x_ecl * x_ecl + y_ecl * y_ecl).sqrt());
let mut longitude = lon_rad.to_degrees();
let latitude = lat_rad.to_degrees();
if longitude < 0.0 {
longitude += 360.0;
} else if longitude >= 360.0 {
longitude -= 360.0;
}
(longitude, latitude)
}
pub fn moon_phase_angle(datetime: DateTime<Utc>) -> f64 {
let (moon_lon, _) = moon_position(datetime);
let jd = julian_date(datetime);
use crate::time_scales::utc_to_tt_jd;
let tt = utc_to_tt_jd(jd);
let (earth_h, _earth_b) = erfars::ephemerides::Epv00(tt, 0.0);
let sun_x = -earth_h[0];
let sun_y = -earth_h[1];
let sun_z = -earth_h[2];
let eps_rad = erfars::precnutpolar::Obl06(tt, 0.0);
let cos_eps = eps_rad.cos();
let sin_eps = eps_rad.sin();
let sun_x_ecl = sun_x;
let sun_y_ecl = cos_eps * sun_y + sin_eps * sun_z;
let sun_lon_rad = sun_y_ecl.atan2(sun_x_ecl);
let mut sun_lon = sun_lon_rad.to_degrees();
if sun_lon < 0.0 {
sun_lon += 360.0;
}
let mut phase = moon_lon - sun_lon;
if phase < 0.0 {
phase += 360.0;
} else if phase >= 360.0 {
phase -= 360.0;
}
phase
}
pub fn moon_illumination(datetime: DateTime<Utc>) -> f64 {
let phase_angle = moon_phase_angle(datetime);
let phase_rad = phase_angle.to_radians();
let illumination = 50.0 * (1.0 - phase_rad.cos());
illumination.clamp(0.0, 100.0)
}
pub fn moon_phase_name(datetime: DateTime<Utc>) -> &'static str {
let phase = moon_phase_angle(datetime);
match phase {
p if p < 22.5 => "New Moon",
p if p < 67.5 => "Waxing Crescent",
p if p < 112.5 => "First Quarter",
p if p < 157.5 => "Waxing Gibbous",
p if p < 202.5 => "Full Moon",
p if p < 247.5 => "Waning Gibbous",
p if p < 292.5 => "Last Quarter",
p if p < 337.5 => "Waning Crescent",
_ => "New Moon",
}
}
pub fn moon_distance(datetime: DateTime<Utc>) -> f64 {
let jd = julian_date(datetime);
use crate::time_scales::utc_to_tt_jd;
let tt = utc_to_tt_jd(jd);
let pv = erfars::ephemerides::Moon98(tt, 0.0);
let x = pv[0];
let y = pv[1];
let z = pv[2];
let distance_au = (x * x + y * y + z * z).sqrt();
distance_au * 149_597_870.7
}
pub fn moon_equatorial(datetime: DateTime<Utc>) -> (f64, f64) {
let jd = julian_date(datetime);
use crate::time_scales::utc_to_tt_jd;
let tt = utc_to_tt_jd(jd);
let pv = erfars::ephemerides::Moon98(tt, 0.0);
let x = pv[0];
let y = pv[1];
let z = pv[2];
let ra_rad = y.atan2(x);
let dec_rad = z.atan2((x * x + y * y).sqrt());
let mut ra_deg = ra_rad.to_degrees();
if ra_deg < 0.0 {
ra_deg += 360.0;
} else if ra_deg >= 360.0 {
ra_deg -= 360.0;
}
(ra_deg, dec_rad.to_degrees())
}
#[cfg(test)]
mod tests {
use super::*;
use chrono::{TimeZone, Utc};
#[test]
fn test_moon_phase_angle() {
let new_moon = Utc.with_ymd_and_hms(2024, 1, 11, 11, 57, 0).unwrap();
let phase = moon_phase_angle(new_moon);
assert!(!(10.0..=350.0).contains(&phase));
let full_moon = Utc.with_ymd_and_hms(2024, 1, 25, 17, 54, 0).unwrap();
let phase = moon_phase_angle(full_moon);
assert!(phase > 170.0 && phase < 190.0); }
#[test]
fn test_moon_illumination() {
let new_moon = Utc.with_ymd_and_hms(2024, 1, 11, 11, 57, 0).unwrap();
let illum = moon_illumination(new_moon);
assert!(illum < 5.0);
let full_moon = Utc.with_ymd_and_hms(2024, 1, 25, 17, 54, 0).unwrap();
let illum = moon_illumination(full_moon);
assert!(illum > 95.0);
}
#[test]
fn test_moon_phase_names() {
let new_moon = Utc.with_ymd_and_hms(2024, 1, 11, 12, 0, 0).unwrap();
assert_eq!(moon_phase_name(new_moon), "New Moon");
let first_quarter = Utc.with_ymd_and_hms(2024, 1, 18, 3, 0, 0).unwrap();
let phase_name = moon_phase_name(first_quarter);
assert!(phase_name == "First Quarter" || phase_name == "Waxing Crescent");
}
#[test]
fn test_moon_distance() {
let dt = Utc.with_ymd_and_hms(2024, 8, 4, 12, 0, 0).unwrap();
let distance = moon_distance(dt);
assert!(distance > 356000.0 && distance < 407000.0);
}
#[test]
fn test_moon_equatorial() {
let dt = Utc.with_ymd_and_hms(2024, 8, 4, 12, 0, 0).unwrap();
let (ra, dec) = moon_equatorial(dt);
assert!((0.0..360.0).contains(&ra));
assert!((-90.0..=90.0).contains(&dec)); }
}