Batch position computation for time-grid scanning.
Bypasses the full SnapshotPipeline (no sky positions, no horizon geometry)
but reproduces its observer line-of-sight when an :observer is supplied,
so the grid and the refinement position_fn share a single coordinate frame.
Computes ecliptic longitudes in the true-of-date ecliptic frame and equatorial declinations from the ICRF vector, optimized for scanning hundreds of timestamps.
Frames
- Geocentric (no observer): the body's geocentric ICRF vector is converted directly. Use this when parallax does not matter.
- Topocentric (observer supplied): the observer's WGS84 position is rotated
into the inertial (ICRF/J2000) frame via GMST — matching
EphCore.SnapshotPipeline.ObserverPositionwithearth_orientation: :gmstandprecession_nutation: false— then subtracted from the geocentric vector before the ecliptic/declination conversion. This is the same geometry the refinement path produces (SkyPosition.topocentric_ecliptic_longitudewith the defaultecliptic_frame: :true_of_date), so grid minima and refined exacts agree to within scanning tolerance.
Motion rates are derived via central difference on the longitude array — this reuses already-computed positions rather than making extra SPK calls.
Call add_horizon_geometry/2 after compute/3 to attach MC/ASC, alt/az, LST,
and obliquity columns for chart overlay workflows. Refinement calls can use the
full EphCore.SnapshotPipeline.observe/4 via a position_fn injection pattern.
Summary
Functions
Augment a position grid with observer horizon geometry for each timestamp.
Compute ecliptic longitudes, equatorial declinations, and motion rates for a batch of timestamps.
Types
@type grid_data() :: %{ optional(:timestamps) => [DateTime.t()], optional(:positions) => %{required(atom()) => [float()]}, optional(:declinations) => %{required(atom()) => [float()]}, optional(:motion) => %{required(atom()) => [float()]}, optional(:axes) => %{mc: [float()], asc: [float()]}, optional(:altitudes) => %{required(atom()) => [float()]}, optional(:azimuths) => %{required(atom()) => [float()]}, optional(:lst) => [float()], optional(:obliquity_deg) => [float()], optional(:observer_lat) => float() }
Functions
Augment a position grid with observer horizon geometry for each timestamp.
Adds MC/ASC ecliptic longitudes, LST, obliquity, and per-body altitude/azimuth columns derived from the grid's topocentric ecliptic longitudes. Intended for chart and event-search overlays built on top of a scanned grid.
Arguments
grid_data— result ofcompute/3(must include:timestampsand:positions)observer—%{lat: float, lon: float, height: float}(WGS84)
Returns
The input map extended with:
:axes—%{mc: [float()], asc: [float()]}:altitudes,:azimuths—%{body_atom => [float()]}:lst,:obliquity_deg— parallel arrays:observer_lat— observer latitude in degrees
@spec compute([DateTime.t()], [atom()], keyword()) :: {:ok, grid_data()} | {:error, term()}
Compute ecliptic longitudes, equatorial declinations, and motion rates for a batch of timestamps.
Arguments
timestamps— list of UTCDateTimestructs, assumed evenly spacedbodies— list of body atoms (e.g.[:venus, :sun, :moon])opts— keyword list::observer—%{lat: float, lon: float, height: float}(WGS84). When present, longitudes and declinations are topocentric; when absent ornil, they are geocentric.
Returns
{:ok, grid_data} where grid_data has:
timestamps— the input list (unchanged)positions—%{body_atom => [lon_deg_t0, lon_deg_t1, ...]}(true-of-date ecliptic)declinations—%{body_atom => [dec_deg_t0, dec_deg_t1, ...]}(J2000 equatorial)motion—%{body_atom => [rate_deg_per_day_t0, ...]}
All arrays are parallel: index i corresponds to timestamps[i].
Motion rates are in degrees/day (positive = direct, negative = retrograde).