Library to help working with SPICE and ERFA libraries
Real osculating orbits at a real epoch, drawn from JPL DE442 ephemerides by
examples/orbits.livemd.
The nearby-star map is generated by
examples/stars.livemd
from published catalog astrometry propagated through Astro.Star.
It is a bit more complicated then normal lib so pay attention:
- use Elixir 1.16+ on Erlang/OTP 25+
- instal ERFA library
- use
x86_64-linux; CSPICE N0067 is bundled, so compilation does not download the toolkit. The included Nix flake dev shell supplies the C toolchain and ERFA on NixOS. - add
ex_astrotomix.exs;reqis optional and only needed for themix astro.kernelsdownloader
def deps do
[
...
{:ex_astro, "~> 0.3"},
{:req, "~> 0.7"},
...
]
end- download SPICE kernels into your application's
priv/kernels/; applications load configured paths when they start
mix astro.kernelsConfigure kernels that should load when the application starts. Resolve them with
Application.app_dir/2 in config/runtime.exs (replace :my_app with your application) so the
paths do not depend on the working directory of a release. The examples below need at least the
leap-second kernel, planetary constants, the DE442 ephemeris, and gm_de440.tpc, which supplies
the gravitational parameters Astro.Orbit.osculating/4 reads when no :mu is given:
config :ex_astro,
spice_kernels:
for path <- [
"priv/kernels/lsk/naif0012.tls",
"priv/kernels/pck/pck00011.tpc",
"priv/kernels/pck/gm_de440.tpc",
"priv/kernels/spk/planets/de442.bsp"
],
do: Application.app_dir(:my_app, path)Kernels downloaded after startup can be managed at runtime:
:ok = Astro.Kernel.load("/path/to/kernel.bsp")
{:ok, paths} = Astro.Kernel.loaded()
:ok = Astro.Kernel.unload("/path/to/kernel.bsp")Missing configured files log a warning instead of preventing application startup, so they can be downloaded and loaded later. Kernel mutations are atomic against the library's single CSPICE pool and safe while other Astro calls are running.
mix astro.kernels skips kernels that already exist, except pck/earth_latest_high_prec.bpc. NAIF
regenerates that high-precision Earth orientation kernel about twice a week, extending the measured
data and the prediction that follows it, so every run downloads it again. Rerun the task regularly
and restart the application (or Astro.Kernel.unload/1 and load/1 the file) to use the new data.
Every download must start with the SPICE ID word of its kernel type and, except for the two kernels
NAIF replaces in place (pck/earth_latest_high_prec.bpc and lsk/latest_leapseconds.tls), match
the SHA-256 pinned in Astro.Kernel.Catalog. A download that fails either check is discarded and
reported; kernels already on disk are not re-hashed.
Astro.Time represents Julian Dates as two-part tuples {jd1, jd2} rather than a single float.
This follows ERFA/SOFA conventions and preserves much more precision for time-scale conversions.
iex> jd = Astro.Time.to_julian_date(~N[2000-01-01 12:00:00])
iex> jd
{2451544.5, 0.5}
iex> Astro.Time.day2sec(jd)
0.0Astro.Ephemeris looks up SPICE body states as Astro.State structs: position in km and
velocity in km/s. Astro.Orbit converts states to named osculating elements and back, and adds
propagation, derived quantities, anomaly calculations, and perifocal geometry:
# UTC timestamp -> SPICE ephemeris time
et = Astro.Time.to_et(~U[2026-08-14 00:00:00Z])
# state of the Earth-Moon barycenter relative to the Sun
{:ok, %Astro.State{position: {x, y, z}, velocity: {vx, vy, vz}}, light_time_s} =
Astro.Ephemeris.spkezr("3", et, "ECLIPJ2000", "NONE", "10")
# osculating orbit of Earth around the Sun in the ecliptic frame
{:ok, orbit} =
Astro.Orbit.osculating("3", "10", et, frame: "ECLIPJ2000")
semi_major_axis_km = Astro.Orbit.semi_major_axis(orbit)
period_seconds = Astro.Orbit.period(orbit)Astro.Support handles the metadata around these calls: body name/ID translation (bodn2c/1,
bodc2n/1), scalar gravitational parameters (gm/1), general kernel-pool constants (bodvcd/2,
bodvrd/2), and SPK file inspection (spkobj/1).
Astro.Star propagates caller-supplied Gaia, Hipparcos, and other star-catalog entries between
two-part TDB Julian Date epochs. It also converts catalog coordinates to and from six-element BCRS
position/velocity vectors. Catalog data is not bundled with the library.
iex> Astro.Star.starpv(ra, dec, pm_ra, pm_dec, parallax, radial_velocity)
{:ok, [x, y, z, vx, vy, vz]}The examples/ directory contains
Livebooks that explain and run the library together:
orbits.livemd- draws the SVG orbit diagrams above from JPL ephemeridesstars.livemd- draws the rotating 3D map of the 100 nearest stellar systems
Open them in Livebook. The notebooks download their data on first run and write the SVG snapshots next to themselves.