use crate::bodies::solar_system::Moon;
use crate::calculus::ephemeris::Ephemeris;
use crate::calculus::horizontal;
use crate::calculus::lunar::phase::{
find_phase_events, illumination_above, illumination_below, illumination_range,
moon_phase_geocentric, moon_phase_topocentric, MoonPhaseGeometry, PhaseEvent, PhaseSearchOpts,
};
use crate::coordinates::transform::context::DefaultEphemeris;
use crate::coordinates::transform::TransformFrame;
use crate::coordinates::{cartesian, centers::*, frames, spherical};
use crate::qtty::{
AstronomicalUnits, IlluminationFractions, Kilometer, LengthUnit, Meter, Quantity,
};
use crate::time::{JulianDate, ModifiedJulianDate, Period};
impl Moon {
pub fn get_apparent_topocentric_equ<U: LengthUnit>(
jd: JulianDate,
site: Geodetic<frames::ECEF>,
) -> spherical::Position<Topocentric, frames::EquatorialTrueOfDate, U>
where
Quantity<U>: From<Quantity<Meter>> + From<Quantity<Kilometer>> + From<AstronomicalUnits>,
{
let moon_geo_ecliptic: cartesian::Position<
Geocentric,
frames::EclipticMeanJ2000,
Kilometer,
> = DefaultEphemeris::moon_geocentric(jd);
let moon_geo_eq_j2000: cartesian::Position<
Geocentric,
frames::EquatorialMeanJ2000,
Kilometer,
> = TransformFrame::to_frame(&moon_geo_ecliptic);
let topo_sph_km: spherical::Position<Topocentric, frames::EquatorialTrueOfDate, Kilometer> =
horizontal::geocentric_j2000_to_apparent_topocentric::<Kilometer>(
&moon_geo_eq_j2000,
site,
jd,
);
let dist_u: Quantity<U> = topo_sph_km.distance.into();
affn::spherical::Position::<Topocentric, frames::EquatorialTrueOfDate, U>::new_unchecked_with_params(
*topo_sph_km.center_params(),
topo_sph_km.polar,
topo_sph_km.azimuth,
dist_u,
)
}
pub fn get_horizontal<U: LengthUnit>(
time: impl Into<JulianDate>,
site: Geodetic<frames::ECEF>,
) -> spherical::Position<Topocentric, frames::Horizontal, U>
where
Quantity<U>: From<Quantity<Meter>> + From<Quantity<Kilometer>> + From<AstronomicalUnits>,
{
let jd = time.into();
let eq = Self::get_apparent_topocentric_equ::<U>(jd, site);
horizontal::equatorial_to_horizontal(&eq, site, jd)
}
}
impl Moon {
pub fn phase_geocentric(time: impl Into<JulianDate>) -> MoonPhaseGeometry {
moon_phase_geocentric::<DefaultEphemeris>(time.into())
}
pub fn phase_topocentric(
time: impl Into<JulianDate>,
site: Geodetic<frames::ECEF>,
) -> MoonPhaseGeometry {
moon_phase_topocentric::<DefaultEphemeris>(time.into(), site)
}
pub fn phase_events(
window: Period<ModifiedJulianDate>,
opts: PhaseSearchOpts,
) -> Vec<PhaseEvent> {
find_phase_events::<DefaultEphemeris>(window, opts)
}
pub fn illumination_above(
window: Period<ModifiedJulianDate>,
k_min: IlluminationFractions,
opts: PhaseSearchOpts,
) -> Vec<Period<ModifiedJulianDate>> {
illumination_above::<DefaultEphemeris>(window, k_min, opts)
}
pub fn illumination_below(
window: Period<ModifiedJulianDate>,
k_max: IlluminationFractions,
opts: PhaseSearchOpts,
) -> Vec<Period<ModifiedJulianDate>> {
illumination_below::<DefaultEphemeris>(window, k_max, opts)
}
pub fn illumination_range(
window: Period<ModifiedJulianDate>,
k_min: IlluminationFractions,
k_max: IlluminationFractions,
opts: PhaseSearchOpts,
) -> Vec<Period<ModifiedJulianDate>> {
illumination_range::<DefaultEphemeris>(window, k_min, k_max, opts)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::coordinates::centers::Geodetic;
use crate::coordinates::frames::ECEF;
use crate::qtty::*;
use crate::time::{JulianDate, ModifiedJulianDate};
fn greenwich() -> Geodetic<ECEF> {
Geodetic::<ECEF>::new(0.0 * DEG, 51.48 * DEG, 0.0 * M)
}
fn one_month() -> Period<ModifiedJulianDate> {
let start = ModifiedJulianDate::from(JulianDate::J2000);
Period::new(start, start + Days::new(30.0))
}
#[test]
fn phase_topocentric_illuminated_fraction_bounded() {
let geom = Moon::phase_topocentric(JulianDate::J2000, greenwich());
assert!(
geom.illuminated_fraction.value() >= 0.0 && geom.illuminated_fraction.value() <= 1.0
);
}
#[test]
fn illumination_above_returns_periods() {
let periods = Moon::illumination_above(
one_month(),
IlluminationFractions::new(0.0),
PhaseSearchOpts::default(),
);
assert!(!periods.is_empty());
}
#[test]
fn illumination_below_returns_periods() {
let periods = Moon::illumination_below(
one_month(),
IlluminationFractions::new(1.0),
PhaseSearchOpts::default(),
);
assert!(!periods.is_empty());
}
#[test]
fn illumination_range_returns_periods() {
let periods = Moon::illumination_range(
one_month(),
IlluminationFractions::new(0.0),
IlluminationFractions::new(1.0),
PhaseSearchOpts::default(),
);
assert!(!periods.is_empty());
}
#[test]
fn illumination_above_empty_when_impossible() {
let periods = Moon::illumination_above(
one_month(),
IlluminationFractions::new(1.01),
PhaseSearchOpts::default(),
);
assert!(periods.is_empty());
}
#[test]
fn illumination_below_empty_when_impossible() {
let periods = Moon::illumination_below(
one_month(),
IlluminationFractions::new(-0.01),
PhaseSearchOpts::default(),
);
assert!(periods.is_empty());
}
}