Apparent-place correction: light-time retardation + annual aberration.
Applies the two corrections that distinguish apparent place from the astrometric (geometric) position computed by the snapshot pipeline (it omits gravitational deflection, which is negligible):
- Light-time — the body is seen where it was when the light left it.
Iterate
tau = range / c, re-evaluating the body's barycentric position at the retarded epochjd_tt - tau(1–2 rounds converge well inside a second). - Annual aberration — the apparent direction is tilted toward the
observer's velocity. We use Earth's barycentric velocity (the dominant
~20.5" annual term; the ~0.3" diurnal term is dropped) and Skyfield's exact
relativistic
add_aberrationformula so the two engines agree.
The observer geometry (ECEF → inertial rotation and the local east/north/up
basis) is built from the same IAU 2006/2000A precession-nutation matrix and
local-basis definition the validated snapshot pipeline uses
(EphCore.SnapshotPipeline.EarthOrientation with :gast + precession-nutation,
and EphCore.Geometry.Horizontal.local_basis/3).
Usage
frame = ApparentPlace.frame(utc_datetime, %{lat: ..., lon: ..., height: ...})
%{alt_deg: alt, az_deg: az, hour_angle_deg: ha, range_km: r} =
ApparentPlace.look(frame, :moon)Per-timestamp work shared across all bodies (earth state, Earth velocity,
nutation, observer position/basis, sidereal time) is computed once in
frame/2; per-body light-time + aberration is computed in look/2.
Summary
Functions
Apparent ecliptic longitude/latitude for body in both geocentric and topocentric variants.
Speed of light used by the apparent-place pass, in km/day.
Earth barycentric velocity (km/day) at jd_tt, via central difference.
Add observer-local geometry to a reusable time_context/2.
Compute the observer-independent apparent-place state for one {time, body}.
Apparent topocentric look angles for body from a precomputed frame/2.
Compute the IAU 2000A nutation {Δψ, Δε} (radians) at jd_tt.
Project a shared geocentric apparent-place state into one observer's local sky.
Precompute the per-timestamp Earth context that can be shared across observers and bodies.
Convert a UTC DateTime to its Terrestrial-Time Julian Date.
Types
@type frame() :: %{ utc: DateTime.t(), jd_tt: float(), cache: map(), earth_ssb_km: AstroUtils.Vector.t(), earth_velocity_km_per_day: AstroUtils.Vector.t(), nutation: {float(), float()}, observer_icrf_km: AstroUtils.Vector.t(), local_basis: EphCore.Geometry.Horizontal.local_basis(), eq_of_date_matrix: AstroUtils.Matrix3.t(), last_deg: float() }
@type geocentric_apparent() :: %{ body: atom(), geocentric_icrf_km: AstroUtils.Vector.t(), earth_velocity_km_per_day: AstroUtils.Vector.t() }
@type time_context() :: %{ utc: DateTime.t(), jd_tt: float(), cache: map(), earth_ssb_km: AstroUtils.Vector.t(), earth_velocity_km_per_day: AstroUtils.Vector.t(), nutation: {float(), float()}, eq_of_date_matrix: AstroUtils.Matrix3.t(), gast_deg: float(), ecef_to_icrf: AstroUtils.Matrix3.t() }
Functions
@spec apparent_lon_lat(frame(), atom()) :: %{ geocentric: {float(), float()}, topocentric: {float(), float()} }
Apparent ecliptic longitude/latitude for body in both geocentric and topocentric variants.
Geocentric: light-time correction + annual aberration on the geocentric ICRF vector; no observer subtraction. Matches the almanac-standard apparent geocentric position.
Topocentric: additionally subtracts the observer's geocentric position before aberration, yielding standard observer-corrected apparent ecliptic coordinates.
@spec c_km_per_day() :: float()
Speed of light used by the apparent-place pass, in km/day.
@spec earth_velocity_at(float()) :: AstroUtils.Vector.t()
Earth barycentric velocity (km/day) at jd_tt, via central difference.
@spec frame(DateTime.t(), observer()) :: frame()
@spec frame(time_context(), observer()) :: frame()
Add observer-local geometry to a reusable time_context/2.
@spec frame(DateTime.t(), observer(), keyword()) :: frame()
@spec geocentric_apparent(time_context() | frame(), atom()) :: geocentric_apparent()
Compute the observer-independent apparent-place state for one {time, body}.
This is not a finished topocentric apparent direction: observer subtraction
happens before the final aberration/projection pass in project/2.
Apparent topocentric look angles for body from a precomputed frame/2.
Returns altitude, azimuth (from North through East), local hour angle in
(-180, 180] (0 at upper transit), declination, and topocentric range — all
light-time- and aberration-corrected to match Skyfield's .apparent().altaz().
Compute the IAU 2000A nutation {Δψ, Δε} (radians) at jd_tt.
@spec project(geocentric_apparent(), frame()) :: look()
Project a shared geocentric apparent-place state into one observer's local sky.
@spec time_context(DateTime.t(), keyword()) :: time_context()
Precompute the per-timestamp Earth context that can be shared across observers and bodies.
Options
:nutation— reuse a{Δψ, Δε}pair instead of summing the IAU 2000A series.:earth_velocity— reuse an Earth barycentric velocity vector (km/day).
Both terms vary negligibly over the ~1-hour root-finding brackets, so the almanac scanner computes them once per event and threads them through every refinement frame to avoid re-summing the 106-term nutation series and the extra SPK velocity evaluations on the hot path.
@spec utc_to_jd_tt(DateTime.t()) :: float()
Convert a UTC DateTime to its Terrestrial-Time Julian Date.