Packages

High-precision astronomical calculations with time conversions, coordinate transformations, and caching for NASA JPL ephemeris data.

Current section

Files

Jump to
jpl_ephem lib jpl_ephem.ex
Raw

lib/jpl_ephem.ex

defmodule JPLEphem do
@moduledoc """
JPL Ephemeris Library for Elixir - Main API Module
This module provides a unified interface to the JPL Ephemeris library,
offering convenient access to astronomical calculations, coordinate
transformations, and time scale conversions.
## Quick Start
# Time conversions
{:ok, jd} = JPLEphem.utc_to_jd(DateTime.utc_now())
{:ok, datetime} = JPLEphem.jd_to_utc(jd)
# Coordinate transformations
{:ok, ecliptic} = JPLEphem.icrf_to_ecliptic({1.0, 0.0, 0.0})
{:ok, {ra, dec}} = JPLEphem.to_ra_dec({1.0, 0.0, 0.0})
# Caching
JPLEphem.cache_put(:mars, position_data, 3600)
{:ok, data} = JPLEphem.cache_get(:mars)
## Modules
- `JPLEphem.Time` - Time scale conversions and Julian Day calculations
- `JPLEphem.Coord` - Coordinate system transformations and vector math
- `JPLEphem.Cache` - High-performance caching with TTL support
"""
alias JPLEphem.{Time, Coord, Cache}
@type vector3 :: {float(), float(), float()}
@type datetime :: DateTime.t()
@type julian_day :: float()
@type cache_key :: any()
@type cache_value :: any()
# Time conversion functions
@doc """
Convert UTC datetime to Julian Day.
## Examples
iex> JPLEphem.utc_to_jd(~U[2025-01-01 12:00:00Z])
{:ok, 2460677.0}
"""
@spec utc_to_jd(datetime()) :: {:ok, julian_day()} | {:error, atom()}
def utc_to_jd(datetime), do: Time.utc_to_jd(datetime)
@doc """
Convert Julian Day to UTC datetime.
## Examples
iex> JPLEphem.jd_to_utc(2460677.0)
{:ok, ~U[2025-01-01 12:00:00.000000Z]}
"""
@spec jd_to_utc(julian_day()) :: {:ok, datetime()} | {:error, atom()}
def jd_to_utc(jd), do: Time.jd_to_utc(jd)
@doc """
Convert UTC to Terrestrial Time (TT).
## Examples
iex> {:ok, tt_jd} = JPLEphem.utc_to_tt(~U[2025-01-01 12:00:00Z])
iex> is_float(tt_jd) and tt_jd > 2460677.0
true
"""
@spec utc_to_tt(datetime()) :: {:ok, julian_day()} | {:error, atom()}
def utc_to_tt(datetime), do: Time.utc_to_tt(datetime)
@doc """
Convert Terrestrial Time (TT) to Barycentric Dynamical Time (TDB).
## Examples
iex> {:ok, tdb_jd} = JPLEphem.tt_to_tdb(2460677.0)
iex> is_float(tdb_jd) and tdb_jd > 2460676.0 and tdb_jd < 2460678.0
true
"""
@spec tt_to_tdb(julian_day()) :: {:ok, julian_day()} | {:error, atom()}
def tt_to_tdb(tt_jd), do: Time.tt_to_tdb(tt_jd)
@doc """
Get leap seconds for a given UTC datetime.
## Examples
iex> {:ok, leap_secs} = JPLEphem.leap_seconds_at(~U[2025-01-01 12:00:00Z])
iex> is_integer(leap_secs) and leap_secs > 30
true
"""
@spec leap_seconds_at(datetime()) :: {:ok, integer()} | {:error, atom()}
def leap_seconds_at(datetime), do: Time.leap_seconds_at(datetime)
# Coordinate transformation functions
@doc """
Transform coordinates from ICRF to Ecliptic system.
## Examples
iex> JPLEphem.icrf_to_ecliptic({1.0, 0.0, 0.0})
{:ok, {1.0, 0.0, 0.0}}
"""
@spec icrf_to_ecliptic(vector3()) :: {:ok, vector3()} | {:error, atom()}
def icrf_to_ecliptic(vector), do: Coord.icrf_to_ecliptic(vector)
@doc """
Transform coordinates from Ecliptic to ICRF system.
## Examples
iex> JPLEphem.ecliptic_to_icrf({1.0, 0.0, 0.0})
{:ok, {1.0, 0.0, 0.0}}
"""
@spec ecliptic_to_icrf(vector3()) :: {:ok, vector3()} | {:error, atom()}
def ecliptic_to_icrf(vector), do: Coord.ecliptic_to_icrf(vector)
@doc """
Convert Cartesian to spherical coordinates.
Uses physics convention where phi is polar angle from Z-axis.
## Examples
iex> JPLEphem.to_spherical({3.0, 4.0, 0.0})
{:ok, {5.0, 0.9272952180016122, 1.5707963267948966}}
"""
@spec to_spherical(vector3()) :: {:ok, {float(), float(), float()}} | {:error, atom()}
def to_spherical(vector), do: Coord.to_spherical(vector)
@doc """
Convert spherical to Cartesian coordinates.
Uses physics convention where phi is polar angle from Z-axis.
## Examples
iex> {:ok, {x, y, z}} = JPLEphem.from_spherical(5.0, 0.9272952180016122, 1.5707963267948966)
iex> abs(x - 3.0) < 1.0e-10 and abs(y - 4.0) < 1.0e-10 and abs(z) < 1.0e-10
true
"""
@spec from_spherical(float(), float(), float()) :: {:ok, vector3()} | {:error, atom()}
def from_spherical(r, theta, phi), do: Coord.from_spherical(r, theta, phi)
@doc """
Convert Cartesian coordinates to Right Ascension and Declination.
## Examples
iex> JPLEphem.to_ra_dec({1.0, 0.0, 0.0})
{:ok, {0.0, 0.0}}
"""
@spec to_ra_dec(vector3()) :: {:ok, {float(), float()}} | {:error, atom()}
def to_ra_dec(vector), do: Coord.to_ra_dec(vector)
@doc """
Convert Right Ascension and Declination to Cartesian coordinates.
## Examples
iex> JPLEphem.from_ra_dec({0.0, 0.0}, 1.0)
{:ok, {1.0, 0.0, 0.0}}
"""
@spec from_ra_dec({float(), float()}, float()) :: {:ok, vector3()} | {:error, atom()}
def from_ra_dec(ra_dec, distance), do: Coord.from_ra_dec(ra_dec, distance)
@doc """
Convert AU to kilometers.
## Examples
iex> JPLEphem.au_to_km({1.0, 0.5, 0.0})
{149597870.7, 74798935.35, 0.0}
"""
@spec au_to_km(vector3()) :: vector3()
def au_to_km(vector), do: Coord.au_to_km(vector)
@doc """
Convert kilometers to AU.
## Examples
iex> JPLEphem.km_to_au({149597870.7, 74798935.35, 0.0})
{1.0, 0.5, 0.0}
"""
@spec km_to_au(vector3()) :: vector3()
def km_to_au(vector), do: Coord.km_to_au(vector)
# Vector mathematics
@doc """
Calculate vector magnitude.
## Examples
iex> JPLEphem.vector_magnitude({3.0, 4.0, 0.0})
5.0
"""
@spec vector_magnitude(vector3()) :: float()
def vector_magnitude(vector), do: Coord.vector_magnitude(vector)
@doc """
Normalize a vector to unit length.
## Examples
iex> {x, y, z} = JPLEphem.normalize_vector({3.0, 4.0, 0.0})
iex> abs(x - 0.6) < 1.0e-10 and abs(y - 0.8) < 1.0e-10 and abs(z) < 1.0e-10
true
"""
@spec normalize_vector(vector3()) :: vector3() | {:error, atom()}
def normalize_vector(vector), do: Coord.normalize_vector(vector)
@doc """
Calculate dot product of two vectors.
## Examples
iex> JPLEphem.dot_product({1.0, 2.0, 3.0}, {4.0, 5.0, 6.0})
32.0
"""
@spec dot_product(vector3(), vector3()) :: float()
def dot_product(v1, v2), do: Coord.dot_product(v1, v2)
@doc """
Calculate cross product of two vectors.
## Examples
iex> JPLEphem.cross_product({1.0, 0.0, 0.0}, {0.0, 1.0, 0.0})
{0.0, 0.0, 1.0}
"""
@spec cross_product(vector3(), vector3()) :: vector3()
def cross_product(v1, v2), do: Coord.cross_product(v1, v2)
# Cache functions
@doc """
Store a value in the cache with TTL.
## Examples
iex> JPLEphem.cache_put(:doctest_key, {{1.0, 2.0, 3.0}, {0.1, 0.2, 0.3}}, 3600)
:ok
"""
@spec cache_put(cache_key(), cache_value(), number()) :: :ok
def cache_put(key, value, ttl \\ nil), do: Cache.put(key, value, ttl)
@doc """
Retrieve a value from the cache.
## Examples
iex> JPLEphem.cache_put(:doctest_get, {{1.0, 2.0, 3.0}, {0.1, 0.2, 0.3}}, 3600)
iex> JPLEphem.cache_get(:doctest_get)
{:ok, {{1.0, 2.0, 3.0}, {0.1, 0.2, 0.3}}}
"""
@spec cache_get(cache_key()) :: {:ok, cache_value()} | :not_found
def cache_get(key), do: Cache.get(key)
@doc """
Delete a key from the cache.
## Examples
iex> JPLEphem.cache_put(:doctest_del, {1, 2, 3}, 3600)
iex> JPLEphem.cache_delete(:doctest_del)
:ok
"""
@spec cache_delete(cache_key()) :: :ok
def cache_delete(key), do: Cache.delete(key)
@doc """
Clear all cache entries.
## Examples
iex> JPLEphem.cache_clear()
:ok
"""
@spec cache_clear() :: :ok
def cache_clear(), do: Cache.clear()
@doc """
Get cache statistics.
## Examples
iex> stats = JPLEphem.cache_stats()
iex> is_map(stats) and Map.has_key?(stats, :size) and Map.has_key?(stats, :hits)
true
"""
@spec cache_stats() :: map()
def cache_stats(), do: Cache.stats()
@doc """
Get current cache size.
## Examples
iex> size = JPLEphem.cache_size()
iex> is_integer(size) and size >= 0
true
"""
@spec cache_size() :: non_neg_integer()
def cache_size(), do: Cache.size()
@doc """
Trigger garbage collection of expired cache entries.
## Examples
iex> JPLEphem.cache_gc()
:ok
"""
@spec cache_gc() :: :ok
def cache_gc(), do: Cache.gc()
# Convenience functions
@doc """
Get current time as Julian Day.
## Examples
iex> {:ok, jd} = JPLEphem.now_jd()
iex> is_float(jd)
true
"""
@spec now_jd() :: {:ok, julian_day()} | {:error, atom()}
def now_jd(), do: utc_to_jd(DateTime.utc_now())
@doc """
Get current time in Terrestrial Time (TT).
## Examples
iex> {:ok, tt} = JPLEphem.now_tt()
iex> is_float(tt)
true
"""
@spec now_tt() :: {:ok, julian_day()} | {:error, atom()}
def now_tt(), do: utc_to_tt(DateTime.utc_now())
@doc """
Get current time in Barycentric Dynamical Time (TDB).
## Examples
iex> {:ok, tdb} = JPLEphem.now_tdb()
iex> is_float(tdb)
true
"""
@spec now_tdb() :: {:ok, julian_day()} | {:error, atom()}
def now_tdb() do
with {:ok, tt} <- now_tt(),
{:ok, tdb} <- tt_to_tdb(tt) do
{:ok, tdb}
end
end
end