use crate::{
coord::{self, Coord},
sol, time,
};
#[derive(Copy, Clone)]
pub struct Moon {
pub epoch: f64,
pub l0: f64,
pub n0: f64,
pub i: time::Angle,
pub e: f64,
pub a: f64,
pub theta0: time::Angle,
pub pi0: time::Angle,
}
pub const MOON: Moon = Moon {
epoch: 2444238.5, l0: 64.975464,
n0: 151.950429,
i: time::Angle::from_degrees(5.145396),
e: 0.054900,
a: 0.002569562, theta0: time::Angle::from_degrees(0.0013312900722),
pi0: time::Angle::from_degrees(0.0024428825934),
};
impl Moon {
fn mooninfo(self, d: time::Date) -> (time::Angle, coord::Coord, f64) {
let day = d.julian() - self.epoch;
let m = time::Angle::from_degrees(((360.0 / 365.2422) * day) + 278.833540 - 282.596403);
let lambdasun = sol::SUN.location(d).ecliptic(d).0;
let ml = time::Angle::from_degrees(13.1763966 * day + self.l0);
let mm = time::Angle::from_degrees(ml.degrees() - 0.1114041 * day - 349.383063);
let ev = 1.2739 * ((ml - lambdasun) * 2.0 - mm).sin();
let ae = 0.1858 * m.sin();
let mmp = time::Angle::from_degrees(mm.degrees() + ev - ae - (0.37 * m.sin()));
let mec = time::Angle::from_degrees(6.2886 * mmp.sin());
let lp = time::Angle::from_degrees(
ml.degrees() + ev + (6.2886 * mmp.sin()) - ae + (0.214 * (mmp * 2.0).sin()),
);
let lpp = time::Angle::from_degrees(lp.degrees() + (1.3166 * (lp - lambdasun).sin()));
let np = time::Angle::from_degrees(
time::Angle::from_degrees(self.n0 - 0.0529539 * day).degrees() - 0.16 * m.sin(),
);
let lambdamoon =
time::Angle::from_radians(((lpp - np).sin() * self.i.cos()).atan2((lpp - np).cos()))
+ np;
let betamoon = time::Angle::asin((lpp - np).sin() * self.i.sin());
let dist = (self.a * (1.0 - self.e * self.e)) / (1.0 + self.e * (mmp + mec).cos());
(
lpp - lambdasun,
coord::Coord::from_ecliptic(lambdamoon, betamoon, d),
dist,
)
}
pub fn locationcart(self, d: time::Date) -> (f64, f64, f64) {
let (_, crd, dist) = self.mooninfo(d);
Coord::cartesian(crd, dist)
}
pub fn phaseage(self, d: time::Date) -> f64 {
29.53058868 * self.mooninfo(d).0.turns()
}
pub fn illumfrac(self, d: time::Date) -> f64 {
(1.0 - self.mooninfo(d).0.cos()) / 2.0
}
pub fn phaseangle(self, d: time::Date) -> time::Angle {
self.mooninfo(d).0
}
pub fn location(self, d: time::Date) -> coord::Coord {
self.mooninfo(d).1
}
pub fn distance(self, d: time::Date) -> f64 {
self.mooninfo(d).2
}
pub fn angdia(self, d: time::Date) -> time::Angle {
self.theta0 / self.distance(d)
}
pub fn parallax(self, d: time::Date) -> time::Angle {
self.pi0 / self.distance(d)
}
pub fn magnitude(self, d: time::Date) -> f64 {
5.0 * (self.distance(d) / self.illumfrac(d).sqrt()).log10() + 0.21
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_moonlocation() {
assert_eq!(
MOON.location(time::Date::from_julian(2460748.554861)),
coord::Coord::from_equatorial(
time::Angle::from_degminsec(172, 11, 15.7),
time::Angle::from_degminsec(3, 59, 15.2)
)
);
}
#[test]
fn test_moonphase() {
assert_eq!(
MOON.illumfrac(time::Date::from_calendar(
2025,
03,
29,
time::Angle::default()
)),
0.002799062630499616
);
assert_eq!(
MOON.illumfrac(time::Date::from_calendar(
2025,
04,
09,
time::Angle::default()
)),
0.8694887493109439
);
assert_eq!(
MOON.magnitude(time::Date::from_calendar(
2024,
12,
25,
time::Angle::default()
)),
-11.366493493867907
);
}
#[test]
fn test_moondist() {
assert_eq!(
MOON.distance(time::Date::from_julian(2460748.467894)),
0.0026765709280575905
);
assert_eq!(
MOON.angdia(time::Date::from_julian(2460748.467894)),
time::Angle::from_degrees(0.499999999)
);
}
}