use crate::aberration::apply_aberration;
use crate::calendar::julian_centuries;
use crate::coords::{mean_obliquity, normalize_degrees};
use crate::error::{JyotishError, Result};
use crate::nutation::nutation_longitude;
use crate::planet::{Planet, PlanetaryPosition};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[non_exhaustive]
pub enum PositionType {
Geometric,
Astrometric,
Apparent,
}
#[derive(Debug, Clone)]
pub struct TypedPosition {
pub position: PlanetaryPosition,
pub position_type: PositionType,
}
pub fn apparent_position(planet: Planet, jd: f64) -> Result<TypedPosition> {
if matches!(planet, Planet::Sun | Planet::Moon) {
return Err(JyotishError::InvalidParameter(
"use apparent_sun() or apparent_moon() for Sun/Moon".into(),
));
}
let geometric = crate::planetary::compute_position(planet, jd)?;
let mut lon = geometric.longitude_deg;
let mut lat = geometric.latitude_deg;
let dist = geometric.distance_au;
let t = julian_centuries(jd);
let sun_lon = crate::sun::solar_longitude(jd);
let eps = mean_obliquity(t);
let (ab_lon, ab_lat) = apply_aberration(lon, lat, sun_lon, eps);
lon = ab_lon;
lat = ab_lat;
lon = normalize_degrees(lon + nutation_longitude(jd));
Ok(TypedPosition {
position: PlanetaryPosition::new(planet, lon, lat, dist, crate::calendar::jd_to_unix(jd)),
position_type: PositionType::Apparent,
})
}
pub fn apparent_sun(jd: f64) -> TypedPosition {
let mut lon = crate::sun::solar_longitude(jd);
let lat = crate::sun::solar_latitude(jd);
let dist = crate::sun::solar_distance_au(jd);
lon -= 20.4898 / (3600.0 * dist);
lon = normalize_degrees(lon + nutation_longitude(jd));
TypedPosition {
position: PlanetaryPosition::new(
Planet::Sun,
lon,
lat,
dist,
crate::calendar::jd_to_unix(jd),
),
position_type: PositionType::Apparent,
}
}
pub fn apparent_moon(jd: f64) -> TypedPosition {
let mut lon = crate::moon::lunar_longitude(jd);
let mut lat = crate::moon::lunar_latitude(jd);
let dist = crate::moon::lunar_distance_au(jd);
let t = julian_centuries(jd);
let sun_lon = crate::sun::solar_longitude(jd);
let eps = mean_obliquity(t);
let (ab_lon, ab_lat) = apply_aberration(lon, lat, sun_lon, eps);
lon = ab_lon;
lat = ab_lat;
let apparent_lon = normalize_degrees(lon + nutation_longitude(jd));
TypedPosition {
position: PlanetaryPosition::new(
Planet::Moon,
apparent_lon,
lat,
dist,
crate::calendar::jd_to_unix(jd),
),
position_type: PositionType::Apparent,
}
}
pub fn geometric_position(planet: Planet, jd: f64) -> Result<TypedPosition> {
let pos = match planet {
Planet::Sun => crate::sun::solar_position(jd),
Planet::Moon => crate::moon::lunar_position(jd),
_ => crate::planetary::compute_position(planet, jd)?,
};
Ok(TypedPosition {
position: pos,
position_type: PositionType::Geometric,
})
}
#[cfg(test)]
mod tests {
use super::*;
const JD_J2000: f64 = 2_451_545.0;
#[test]
fn apparent_mars() {
let pos = apparent_position(Planet::Mars, JD_J2000).unwrap();
assert_eq!(pos.position_type, PositionType::Apparent);
assert!(pos.position.longitude_deg >= 0.0 && pos.position.longitude_deg < 360.0);
}
#[test]
fn apparent_vs_geometric_differs() {
let geo = geometric_position(Planet::Jupiter, JD_J2000).unwrap();
let app = apparent_position(Planet::Jupiter, JD_J2000).unwrap();
let diff = (geo.position.longitude_deg - app.position.longitude_deg).abs();
assert!(
diff > 0.001 && diff < 0.02,
"diff = {diff}° (should be ~0.006°)"
);
}
#[test]
fn apparent_sun_tagged() {
let pos = apparent_sun(JD_J2000);
assert_eq!(pos.position_type, PositionType::Apparent);
assert_eq!(pos.position.planet, Planet::Sun);
}
#[test]
fn apparent_moon_tagged() {
let pos = apparent_moon(JD_J2000);
assert_eq!(pos.position_type, PositionType::Apparent);
assert_eq!(pos.position.planet, Planet::Moon);
}
#[test]
fn geometric_all_bodies() {
for planet in [
Planet::Sun,
Planet::Moon,
Planet::Mars,
Planet::Jupiter,
Planet::Saturn,
] {
let pos = geometric_position(planet, JD_J2000).unwrap();
assert_eq!(pos.position_type, PositionType::Geometric);
}
}
#[test]
fn sun_moon_rejected_by_apparent() {
assert!(apparent_position(Planet::Sun, JD_J2000).is_err());
assert!(apparent_position(Planet::Moon, JD_J2000).is_err());
}
#[test]
fn position_type_serde() {
let pt = PositionType::Apparent;
let json = serde_json::to_string(&pt).unwrap();
let restored: PositionType = serde_json::from_str(&json).unwrap();
assert_eq!(restored, pt);
}
}