1use crate::assist_data::AssistData;
4use crate::coordinates::{bary_to_helio, equatorial_to_ecliptic};
5use libassist_sys::Ephemeris;
6use libassist_sys::ffi;
7
8use crate::observatory::ObservatoryTable;
9use crate::origin::Origin;
10use crate::{Error, Result};
11
12#[derive(Debug, Clone)]
14pub struct BodyState {
15 pub state: [f64; 6],
17 pub epoch: f64,
19}
20
21pub fn assist_get_state(
37 data: &AssistData,
38 origin: &Origin,
39 epochs: &[f64],
40 num_threads: Option<usize>,
41) -> Result<Vec<BodyState>> {
42 let ephem = &data.ephem;
43 let obs_table = data.observatory.as_ref();
44 let op = |&epoch_mjd: &f64| -> Result<BodyState> {
45 let state = resolve_origin_state(ephem, origin, epoch_mjd, obs_table)?;
46 Ok(BodyState {
47 state,
48 epoch: epoch_mjd,
49 })
50 };
51 crate::propagate::map_with_threads(epochs, num_threads, op)
52}
53
54pub(crate) fn resolve_origin_state(
58 ephem: &Ephemeris,
59 origin: &Origin,
60 epoch_mjd: f64,
61 obs_table: Option<&ObservatoryTable>,
62) -> Result<[f64; 6]> {
63 let t = ephem.mjd_to_assist_time(epoch_mjd);
64
65 if let Some(body_id) = origin.body_id() {
66 let bary_eq = ephem.get_body_state_array(body_id, t)?;
67 let sun = ephem.get_body_state_array(ffi::ASSIST_BODY_SUN, t)?;
68 Ok(equatorial_to_ecliptic(&bary_to_helio(&bary_eq, &sun)))
69 } else if let Origin::SolarSystemBarycenter = origin {
70 let sun = ephem.get_body_state_array(ffi::ASSIST_BODY_SUN, t)?;
72 let helio_eq = [-sun[0], -sun[1], -sun[2], -sun[3], -sun[4], -sun[5]];
73 Ok(equatorial_to_ecliptic(&helio_eq))
74 } else if let Origin::Observatory(code) = origin {
75 let table = obs_table.ok_or_else(|| {
76 Error::Other("Observatory table required for observatory codes".into())
77 })?;
78 compute_observatory_state(ephem, code, t, table)
79 } else {
80 unreachable!()
81 }
82}
83
84fn compute_observatory_state(
86 ephem: &Ephemeris,
87 code: &str,
88 t: f64,
89 obs_table: &ObservatoryTable,
90) -> Result<[f64; 6]> {
91 let entry = obs_table
92 .get(code)
93 .ok_or_else(|| Error::InvalidObservatory(format!("Unknown MPC code: {code}")))?;
94
95 let earth = ephem.get_body_state_array(ffi::ASSIST_BODY_EARTH, t)?;
96 let sun = ephem.get_body_state_array(ffi::ASSIST_BODY_SUN, t)?;
97
98 if entry.is_space_based() || code == "500" {
99 return Ok(equatorial_to_ecliptic(&bary_to_helio(&earth, &sun)));
101 }
102
103 let eo = obs_table
108 .earth_orientation()
109 .ok_or_else(|| Error::MissingEarthOrientation(code.to_string()))?;
110
111 let earth_radius_au = ephem.earth_radius_au();
113 let lon_rad = entry.longitude_deg.to_radians();
114 let (sin_lon, cos_lon) = lon_rad.sin_cos();
115 let bf_x = earth_radius_au * entry.cos_lat * cos_lon;
116 let bf_y = earth_radius_au * entry.cos_lat * sin_lon;
117 let bf_z = earth_radius_au * entry.sin_lat;
118
119 let jd = t + ephem.jd_ref();
122 let et = (jd - 2_451_545.0) * 86_400.0;
123 let r = eo
124 .rotation_itrf_to_j2000_et(et)
125 .map_err(|e| Error::Other(format!("earth orientation lookup: {e}")))?;
126 let obs_x = r[0][0] * bf_x + r[0][1] * bf_y + r[0][2] * bf_z;
128 let obs_y = r[1][0] * bf_x + r[1][1] * bf_y + r[1][2] * bf_z;
129 let obs_z = r[2][0] * bf_x + r[2][1] * bf_y + r[2][2] * bf_z;
130 const OMEGA_EARTH_RAD_S: f64 = 7.292_115_0e-5; const SEC_PER_DAY: f64 = 86_400.0;
136 let omega = OMEGA_EARTH_RAD_S * SEC_PER_DAY; let vb_x = -omega * bf_y;
138 let vb_y = omega * bf_x;
139 let vb_z = 0.0;
140 let obs_vx = r[0][0] * vb_x + r[0][1] * vb_y + r[0][2] * vb_z;
141 let obs_vy = r[1][0] * vb_x + r[1][1] * vb_y + r[1][2] * vb_z;
142 let obs_vz = r[2][0] * vb_x + r[2][1] * vb_y + r[2][2] * vb_z;
143
144 let obs_bary = [
146 earth[0] + obs_x,
147 earth[1] + obs_y,
148 earth[2] + obs_z,
149 earth[3] + obs_vx,
150 earth[4] + obs_vy,
151 earth[5] + obs_vz,
152 ];
153 Ok(equatorial_to_ecliptic(&bary_to_helio(&obs_bary, &sun)))
154}