Sidereon.Ephemeris (Sidereon v0.10.1)

Copy Markdown View Source

JPL/NAIF SPK (DAF .bsp) ephemeris kernel reader.

Computes positions and velocities of solar system bodies, spacecraft, and minor planets from JPL SPK/BSP kernels (DE421, DE440, Horizons exports, etc.).

The kernel is parsed once into a loaded handle by load/1; querying never re-reads the file. Reading and evaluation are delegated to sidereon_core::astro::spk, the validated SPK reader shared by the rest of the engine and by the other language bindings. It evaluates SPK segment types 2 (Chebyshev position), 3 (Chebyshev state), and 21 (Extended Modified Difference Arrays), so DE-series planetary kernels and Horizons spacecraft / asteroid kernels are all supported through the same code path.

Example

{:ok, eph} = Sidereon.Ephemeris.load("de421.bsp")

# Position and velocity at an ephemeris epoch (TDB seconds past J2000):
{:ok, state} = Sidereon.Ephemeris.state(eph, :moon, :earth, 0.0)
state.position_km
state.velocity_km_s

# Convenience: position only, from a DateTime or Julian Date (TDB):
{:ok, {x, y, z}} = Sidereon.Ephemeris.position(eph, :sun, :earth, ~U[2024-01-01 12:00:00Z])

# The parsed segment table:
Sidereon.Ephemeris.segments(eph)

Bodies

Bodies may be given as atoms (:ssb / :solar_system_barycenter, :mercury, :venus, :earth_moon_barycenter / :emb, :mars, :jupiter, :saturn, :uranus, :neptune, :pluto, :sun, :moon, :earth) or as raw NAIF integer codes. Integer codes pass straight through to the reader, which is how spacecraft and minor-planet kernels are queried (e.g. 20000433 for 433 Eros).

Summary

Types

One parsed SPK segment descriptor.

A body-to-center state from a kernel query.

t()

A loaded SPK kernel handle.

Functions

The DAF internal file name recorded in the kernel header.

Load and parse an SPK/BSP ephemeris file into a handle.

Like load/1 but raises on failure.

Parse an SPK/BSP ephemeris kernel from an in-memory byte buffer.

Compute the position of target relative to observer at the given time.

The kernel's parsed segment descriptors, in DAF summary order.

Query the state of target relative to center at ephemeris epoch et_seconds (TDB seconds past J2000).

Types

body()

@type body() :: atom() | integer()

epoch()

@type epoch() :: DateTime.t() | NaiveDateTime.t() | float() | integer()

load_error()

@type load_error() ::
  {:file_error, File.posix()} | {:invalid_path, term()} | {:parse_error, term()}

position_error()

@type position_error() :: state_error() | {:invalid_datetime, term()}

segment()

@type segment() :: %{
  name: String.t(),
  target: integer(),
  center: integer(),
  frame: integer(),
  data_type: integer(),
  start_et: float(),
  stop_et: float(),
  start_address: integer(),
  end_address: integer()
}

One parsed SPK segment descriptor.

state()

@type state() :: %{
  target: integer(),
  center: integer(),
  position_km: vec3(),
  velocity_km_s: vec3() | nil,
  frame: integer()
}

A body-to-center state from a kernel query.

state_error()

@type state_error() ::
  {:invalid_body, term()}
  | {:unknown_body, integer()}
  | {:no_segment_path, integer(), integer()}
  | {:nif_error, term()}

t()

@type t() :: %Sidereon.Ephemeris{handle: reference()}

A loaded SPK kernel handle.

vec3()

@type vec3() :: {float(), float(), float()}

Functions

internal_name(ephemeris)

@spec internal_name(t()) :: String.t()

The DAF internal file name recorded in the kernel header.

load(path)

@spec load(term()) :: {:ok, t()} | {:error, load_error()}

Load and parse an SPK/BSP ephemeris file into a handle.

The file is read and parsed exactly once; the returned handle holds the parsed kernel and is passed to state/4, segments/1, and position/4. Returns {:ok, ephemeris} or {:error, reason} when the file cannot be read or parsed.

Example

{:ok, eph} = Sidereon.Ephemeris.load("/path/to/de421.bsp")

load!(path)

@spec load!(String.t()) :: t()

Like load/1 but raises on failure.

load_bytes(bytes)

@spec load_bytes(binary()) :: {:ok, t()} | {:error, {:parse_error, term()}}

Parse an SPK/BSP ephemeris kernel from an in-memory byte buffer.

Returns {:ok, ephemeris} or {:error, {:parse_error, reason}}.

position(ephemeris, target, observer, datetime)

@spec position(t(), body(), body(), epoch()) ::
  {:ok, vec3()} | {:error, position_error()}

Compute the position of target relative to observer at the given time.

A position-only convenience over state/4 that accepts a calendar epoch. Returns {:ok, {x, y, z}} in km in the J2000/ICRF reference frame, or {:error, reason}.

The target and observer are body atoms (see module docs) or NAIF integer codes. The datetime can be a DateTime, a NaiveDateTime, or a Julian Date (TDB) as a float.

Examples

{:ok, {x, y, z}} = Sidereon.Ephemeris.position(eph, :moon, :earth, datetime)

# Raw NAIF code (433 Eros) against a Horizons kernel:
{:ok, {x, y, z}} = Sidereon.Ephemeris.position(eph, 20_000_433, :sun, jd_tdb)

position!(ephemeris, target, observer, datetime)

@spec position!(t(), body(), body(), epoch()) :: vec3()

Like position/4 but raises on failure.

segments(ephemeris)

@spec segments(t()) :: [segment()]

The kernel's parsed segment descriptors, in DAF summary order.

Each entry is a map with :name, :target, :center, :frame, :data_type, :start_et, :stop_et, :start_address, and :end_address. Coverage epochs (:start_et / :stop_et) are ephemeris (TDB) seconds past J2000.

state(ephemeris, target, center, et_seconds)

@spec state(t(), body(), body(), number()) :: {:ok, state()} | {:error, state_error()}

Query the state of target relative to center at ephemeris epoch et_seconds (TDB seconds past J2000).

This is the primary query, matching the other language bindings: it resolves and chains segments as needed and returns both position and velocity. Returns {:ok, state} where state is a map with :target, :center, :position_km (a {x, y, z} tuple, km), :velocity_km_s (a {vx, vy, vz} tuple in km/s, or nil when the resolved path runs through a position-only type-2 segment), and :frame (the NAIF reference-frame id, J2000/ICRF for standard kernels).

target and center are body atoms (see module docs) or NAIF integer codes.

Returns {:error, {:invalid_body, body}} for an unknown atom, {:error, {:unknown_body, code}} when a body is absent from the kernel, {:error, {:no_segment_path, target, center}} when no segment chain connects them, and {:error, {:nif_error, reason}} when a chain exists but none covers the epoch or the path needs an unsupported segment type.

Example

{:ok, state} = Sidereon.Ephemeris.state(eph, :moon, :earth, 0.0)

state!(ephemeris, target, center, et_seconds)

@spec state!(t(), body(), body(), number()) :: state()

Like state/4 but raises on failure.