EphCore.Corrections.ApparentPlace (eph_core v0.1.0)

Copy Markdown View Source

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):

  1. 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 epoch jd_tt - tau (1–2 rounds converge well inside a second).
  2. 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_aberration formula 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

frame()

@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()
}

geocentric_apparent()

@type geocentric_apparent() :: %{
  body: atom(),
  geocentric_icrf_km: AstroUtils.Vector.t(),
  earth_velocity_km_per_day: AstroUtils.Vector.t()
}

look()

@type look() :: %{
  alt_deg: float(),
  az_deg: float(),
  hour_angle_deg: float(),
  declination_deg: float(),
  range_km: float()
}

observer()

@type observer() :: %{optional(atom()) => float()}

time_context()

@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

apparent_lon_lat(frame, body)

@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.

c_km_per_day()

@spec c_km_per_day() :: float()

Speed of light used by the apparent-place pass, in km/day.

earth_velocity_at(jd_tt)

@spec earth_velocity_at(float()) :: AstroUtils.Vector.t()

Earth barycentric velocity (km/day) at jd_tt, via central difference.

frame(utc, observer)

@spec frame(DateTime.t(), observer()) :: frame()
@spec frame(time_context(), observer()) :: frame()

Add observer-local geometry to a reusable time_context/2.

frame(utc, observer, opts)

@spec frame(DateTime.t(), observer(), keyword()) :: frame()

geocentric_apparent(context, body)

@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.

look(frame, body)

@spec look(frame(), atom()) :: look()

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().

nutation_at(jd_tt)

@spec nutation_at(float()) :: {float(), float()}

Compute the IAU 2000A nutation {Δψ, Δε} (radians) at jd_tt.

project(geocentric_apparent, frame)

@spec project(geocentric_apparent(), frame()) :: look()

Project a shared geocentric apparent-place state into one observer's local sky.

time_context(utc, opts \\ [])

@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.

utc_to_jd_tt(utc)

@spec utc_to_jd_tt(DateTime.t()) :: float()

Convert a UTC DateTime to its Terrestrial-Time Julian Date.