Astro.Ephemeris.Kernel (Astro v2.1.1)

Copy Markdown View Source

Parses JPL DE-series binary SPK ephemeris files (DAF/SPK format).

Supports Type 2 segments (Chebyshev position polynomials), which is the type used for planetary and lunar positions in DE440s and related files.

Usage

# This step is performed automatically at application start
{:ok, kernel} = Astro.Ephemeris.Kernel.load("priv/de440s.bsp")

# Moon wrt EMB
{:ok, seg} = Astro.Ephemeris.Kernel.find_segment(301, 3)
{x, y, z} = Astro.Ephemeris.Kernel.position(seg, dynamical_time)

dynamical_time is TDB seconds past J2000.0 (2000-01-01T12:00:00 TT). Returned {x, y, z} are in km relative to the segment's centre body.

Design notes

The full file binary is stored inside the kernel struct so that position/3 does not re-read the file on every call. For a 32 MB DE440s file this avoids ~thousands of disk reads per rise/set computation.

DAF/SPK Type 2 format

A DAF (Double Precision Array File) consists of 1024-byte records. Record 1 is the file header. Subsequent records form a comment area followed by a doubly-linked list of summary/name record pairs.

Each segment descriptor (summary) contains:

  • ND=2 doubles: start_dt, end_dt (dynamical time — TDB seconds past J2000.0)
  • NI=6 integers packed as raw int32 bytes: target, centre, frame, data_type, start_addr, end_addr

Addresses are 1-based word (8-byte double) indices into the whole file.

A Type 2 segment's data area contains N Chebyshev records followed by 4 metadata words [init_dt (dynamical time), intlen, rsize, n] at the very end. Each Chebyshev record has rsize doubles:

  [t_mid, t_half, cx_0..cx_d, cy_0..cy_d, cz_0..cz_d]

where d = degree = (rsize - 2) / 3 - 1.

Summary

Functions

Returns the loaded ephemeris kernel from :persistent_term storage.

Finds the first segment matching target and centre NAIF IDs.

Loads and parses a JPL DE-series SPK binary ephemeris file.

Evaluates a Type 2 Chebyshev segment at the given dynamical time.

Types

t()

@type t() :: %Astro.Ephemeris.Kernel{
  data: binary(),
  endian: :little | :big,
  path: String.t(),
  segments: [map()]
}

Functions

ephemeris()

Returns the loaded ephemeris kernel from :persistent_term storage.

The kernel is loaded at application start by Astro.Application and stored under ephemeris_key/0.

Returns

find_segment(target, centre, dynamical_time \\ nil)

@spec find_segment(integer(), integer(), float() | nil) ::
  {:ok, map()} | {:error, :not_found}

Finds the first segment matching target and centre NAIF IDs.

If dynamical_time is provided, only segments whose time span covers the given instant are considered. If nil (the default), the first matching segment regardless of time span is returned.

Arguments

  • target is the NAIF body ID of the target body (e.g. 301 for the Moon).

  • centre is the NAIF body ID of the centre body (e.g. 3 for the Earth–Moon Barycenter).

  • dynamical_time is TDB seconds past J2000.0, or nil to match any time span. Defaults to nil.

Returns

  • {:ok, segment} where segment is a map describing the matching Type 2 segment.

  • {:error, :not_found} if no matching segment exists.

load(path)

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

Loads and parses a JPL DE-series SPK binary ephemeris file.

The entire file is read into memory and all Type 2 segment descriptors are extracted so that subsequent position/2 calls do not require disk I/O.

Arguments

  • path is the filesystem path to a DAF/SPK file (e.g. "priv/de440s.bsp").

Returns

  • {:ok, kernel} where kernel is an Astro.Ephemeris.Kernel struct containing the parsed segments and the raw binary data.

  • {:error, reason} if the file cannot be read or is not a valid DAF/SPK file.

position(segment, dynamical_time)

@spec position(map(), float()) :: {float(), float(), float()}

Evaluates a Type 2 Chebyshev segment at the given dynamical time.

Reads the appropriate Chebyshev record from the in-memory kernel data, normalizes the time argument to [-1, +1], and evaluates the polynomial for each Cartesian axis.

Arguments

  • segment is a segment map as returned by find_segment/3.

  • dynamical_time is TDB seconds past J2000.0.

Returns

  • {x, y, z} position in kilometers relative to the segment's centre body.