1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
//! Standalone apparent-position computation for a solar system body.
//!
//! This module provides [`ApparentPosition`] as the public return type and a set
//! of small, composable helper functions that together implement the pipeline:
//!
//! ```text
//! OrbitalElements → propagate → topocentric geometry → aberration → (RA, Dec)
//! ```
//!
//! # Coordinate conventions
//!
//! - Positions in **AU**, velocities in **AU/day**.
//! - Intermediate frames: **ecliptic mean J2000**.
//! - Final output: **equatorial mean J2000** (RA, Dec in radians).
//! - Time: **MJD TT**.
//!
//! # Pipeline steps
//!
//! | Step | Function / Method | Purpose |
//! |------|-------------------|---------|
//! | 1 | [`obs_time_to_epoch`] | Convert MJD TT scalar → [`hifitime::Epoch`] |
//! | 2 | [`PropagatorKind::propagate_to_epoch`](crate::propagator::PropagatorKind::propagate_to_epoch) | Propagate orbit; rotate to equatorial J2000 |
//! | 3 | [`observer_pv`] | Resolve observer heliocentric position **and velocity** |
//! | 4 | [`assemble_apparent_position`] | Compute topocentric vector, apply aberration, convert to (RA, Dec) |
//!
//! # Aberration model
//!
//! The first-order stellar aberration correction shifts the topocentric
//! line-of-sight vector by
//!
//! $$\mathbf{x}\_\text{corr} = \mathbf{x}\_\text{topo}
//! - \frac{|\mathbf{x}\_\text{topo}|}{c}\\,\mathbf{v}\_\text{body}$$
//!
//! where $c$ is the speed of light in AU/day and
//! $\mathbf{v}\_\text{body}$ is the body's heliocentric velocity.
use ;
use Vector3;
use ;
use crate::;
use ;
// ---------------------------------------------------------------------------
// Public return type
// ---------------------------------------------------------------------------
/// Predicted apparent position of a solar system body together with geometric
/// distances.
///
/// The `coord` field contains the predicted equatorial sky position in the
/// **equatorial mean J2000** frame. Because this is a *prediction* rather than
/// a measurement, the error fields `ra_error` and `dec_error` inside `coord`
/// are always set to `0.0`.
///
/// Distances are computed from the unaberrated heliocentric state before the
/// aberration correction is applied to the line-of-sight direction.
// ---------------------------------------------------------------------------
// Intermediate propagated state (shared with geometry module)
// ---------------------------------------------------------------------------
/// Full propagated state at a given epoch, shared between position and geometry
/// computations.
///
/// Holding this intermediate result allows [`compute_with_geometry`] to run a
/// single orbit propagation and observer-position query and hand the results to
/// both [`assemble_apparent_position`] and
/// [`super::geometry::compute_geometry`] without redundant work.
pub
// ---------------------------------------------------------------------------
// Internal helpers — propagation and observer geometry
// ---------------------------------------------------------------------------
/// Propagate the orbit and resolve observer geometry, producing a
/// [`PropagatedState`].
///
/// This is the shared kernel called by [`compute`] and [`compute_with_geometry`].
///
/// # Arguments
///
/// - `elements` – Equinoctial orbital elements.
/// - `obs_time_mjd` – Observation epoch \[MJD TT\].
/// - `fixed_cache` – Pre-built body-fixed observer cache (epoch-invariant).
/// Must have been constructed from the same [`Observer`] that owns this
/// request slot. Building it once per observer slot and reusing it across
/// all epochs avoids redundant trigonometric conversions.
/// - `observer` – Observing site, used only to attach to the result.
/// - `jpl` – JPL planetary ephemeris.
/// - `ut1` – UT1 time-scale provider.
/// - `config` – Ephemeris configuration (propagator, aberration).
///
/// # Errors
///
/// Returns [`OutfitError`] if:
/// - Orbit propagation fails.
/// - The JPL ephemeris data is unavailable for the requested epoch.
/// - The observer geometry cannot be resolved.
pub
// ---------------------------------------------------------------------------
// Entry points
// ---------------------------------------------------------------------------
/// Compute the apparent equatorial position of a solar system body.
///
/// This is the main entry point called by
/// [`OrbitalElements::apparent_position`](crate::OrbitalElements::apparent_position).
/// It propagates the orbit, resolves observer geometry, applies the aberration
/// correction and converts to equatorial coordinates.
///
/// # Arguments
///
/// - `elements` – Equinoctial orbital elements.
/// - `obs_time_mjd`– Observation epoch \[MJD TT\].
/// - `fixed_cache` – Pre-built body-fixed observer cache (epoch-invariant).
/// - `jpl` – JPL planetary ephemeris.
/// - `ut1` – UT1 time-scale provider.
/// - `config` – Ephemeris configuration.
///
/// # Errors
///
/// Returns [`OutfitError`] if propagation or observer geometry fails.
pub
/// Compute both the apparent position and the body geometry in a single
/// propagation pass.
///
/// Called by
/// [`OrbitalElements::apparent_position_and_geometry`](crate::OrbitalElements::apparent_position_and_geometry).
/// The orbit is propagated and observer geometry resolved exactly once; the
/// resulting [`PropagatedState`] is then handed to both
/// [`assemble_apparent_position`] and
/// [`super::geometry::compute_geometry`].
///
/// # Arguments
///
/// - `elements` – Equinoctial orbital elements.
/// - `obs_time_mjd`– Observation epoch \[MJD TT\].
/// - `fixed_cache` – Pre-built body-fixed observer cache (epoch-invariant).
/// - `jpl` – JPL planetary ephemeris.
/// - `ut1` – UT1 time-scale provider.
/// - `config` – Ephemeris configuration.
///
/// # Errors
///
/// Returns [`OutfitError`] if propagation or either assembly step fails.
pub
// ---------------------------------------------------------------------------
// Step 1 – epoch conversion
// ---------------------------------------------------------------------------
/// Convert a scalar MJD TT value to a [`hifitime::Epoch`].
///
/// The time scale is fixed to [`TimeScale::TT`] (Terrestrial Time), which is
/// the time argument used throughout the library for orbit propagation and
/// ephemeris lookups.
// ---------------------------------------------------------------------------
// Step 3 – observer position and velocity
// ---------------------------------------------------------------------------
/// Compute the observer's heliocentric position **and velocity**, the Earth's
/// heliocentric position, all in the equatorial mean J2000 frame.
///
/// # Pre-condition — `fixed_cache` is epoch-invariant
///
/// `fixed_cache` must have been constructed once per observer slot (before the
/// epoch loop) via [`ObserverFixedCache::try_from`]. Passing it in avoids
/// rebuilding the body-fixed geocentric coordinates (sin/cos of longitude, ρ
/// factors) for every epoch.
///
/// # Returns
///
/// `(obs_pos_equ [AU], obs_vel_equ [AU/day], earth_pos_equ [AU])`.
///
/// # Errors
///
/// Returns [`OutfitError`] if the geocentric position computation
/// (`pvobs`) fails or a NaN conversion fails.
type ObserverPv = ;
// ---------------------------------------------------------------------------
// Step 4 – apparent position assembly
// ---------------------------------------------------------------------------
/// Assemble the [`ApparentPosition`] from a [`PropagatedState`].
///
/// The function performs the following sub-steps:
///
/// 1. **Distances** — compute heliocentric and geocentric distances.
/// 2. **Topocentric vector** — $\mathbf{d} = \mathbf{r}\_\text{body} - \mathbf{r}\_\text{obs}$.
/// 3. **Aberration correction** — first-order or second-order, per `aberration`.
/// 4. **Sky coordinates** — convert the corrected direction to (RA, Dec).
///
/// # Errors
///
/// Returns [`OutfitError`] if the second-order aberration back-propagation
/// fails to converge. The first-order path is always infallible.
pub
/// Convert a Cartesian direction vector to an [`EquCoord`] (RA, Dec in radians;
/// error fields set to `0.0`).
///
/// Delegates to [`CartesianCoord`] → [`EquCoord`] conversion, which computes:
/// $$\alpha = \operatorname{atan2}(y, x) \bmod 2\pi, \quad
/// \delta = \operatorname{atan2}\left(z,\\,\sqrt{x^2+y^2}\right)$$
///
/// The magnitude of `v` is irrelevant; only the direction is used.
pub