# `Astro.Ephemeris`
[🔗](https://github.com/kipcole9/astro/blob/v2.9.0/lib/astro/ephemeris/ephemeris.ex#L1)

Computes the apparent geocentric positions of the Moon and the Sun from a
JPL DE440s (or compatible) SPK binary ephemeris kernel.

The SPK file provides positions in ICRF/J2000 Cartesian coordinates (km).
This module chains the available segments to produce each body's position
relative to the Earth's centre, then applies IAU 1980 precession and
nutation to yield apparent geocentric right ascension, declination and
distance in the true equator and equinox of date.

## Segment chaining (DE440s)

`de440s.bsp` supplies, among others:

* body 301, the Moon, relative to body 3, the Earth-Moon barycentre (EMB).

* body 399, the Earth, relative to body 3.

The Moon relative to the Earth is then Moon/EMB − Earth/EMB.

The compact ephemeris bundled with the library omits body 399, which
`Astro.Ephemeris.Kernel` reconstructs from body 301 — the two are exact
scalar multiples of one another. Chaining is unaffected.

## Setup

The kernel is loaded when the application starts, from the path that
`Astro.Ephemeris.Downloader.ephemeris_path/0` resolves, and the position
functions read it from there:

    {:ok, {right_ascension, declination, distance}} =
      Astro.Ephemeris.moon_position(~U[2024-06-21 12:00:00Z])

## Accuracy

Position accuracy is limited by the ephemeris itself: DE440 achieves
sub-centimetre accuracy for the Moon relative to current-epoch laser
ranging data. The dominant remaining error sources for rise and set timing
are:

* the atmospheric refraction model, about 1 arcminute or 2 seconds of time
  near the horizon.

* topocentric correction residuals at high solar-altitude latitudes.

This is a 10–100× improvement over the truncated Chapront series used in
Meeus that was the core of the Astro 1.x library.

# `check_coverage`
*since 2.8.0* 

```elixir
@spec check_coverage(Astro.Time.moment(), Astro.Time.moment() | nil) ::
  :ok | {:error, :not_found}
```

Checks that the loaded ephemeris covers the Sun and the Moon over a
span of moments.

The loaded ephemeris covers one continuous span of years, so a span
is covered when both of its ends are.

### Arguments

* `from` is the first moment (fractional days since epoch) of the span.

* `to` is the last moment of the span. The default is `from`, which
  checks a single moment.

### Returns

* `:ok` if the ephemeris covers the whole span.

* `{:error, :not_found}` if either end lies outside the ephemeris.

### Examples

    iex> Astro.Ephemeris.check_coverage(Astro.Time.date_time_to_moment(~D[2024-06-21]))
    :ok

    iex> Astro.Ephemeris.check_coverage(Astro.Time.date_time_to_moment(~D[1000-01-01]))
    {:error, :not_found}

# `equatorial_horizontal_parallax`

```elixir
@spec equatorial_horizontal_parallax(float()) :: float()
```

Returns the equatorial horizontal parallax for a given geocentric
distance.

Computed as `asin(R_earth / distance)` using the WGS-84
equatorial radius of 6378.137 km.

### Arguments

* `distance_km` is the geocentric distance in kilometers.

### Returns

* The equatorial horizontal parallax in degrees.

### Examples

    iex> Astro.Ephemeris.equatorial_horizontal_parallax(384_400.0) |> Float.round(6)
    0.950721

# `moon_ecliptic_position`

```elixir
@spec moon_ecliptic_position(Astro.Time.moment()) ::
  {:ok, {float(), float(), float()}} | {:error, term()}
```

Computes the apparent geocentric ecliptic position of the Moon for the
given moment.

### Arguments

* `moment` is a moment (fractional days since epoch).

### Returns

* `{:ok, {longitude_deg, latitude_deg, distance_km}}`.

* `{:error, reason}` if a required ephemeris segment is not found.

### Examples

    iex> moment = Astro.Time.date_time_to_moment(~U[2024-06-21 12:00:00Z])
    iex> {:ok, {longitude, latitude, distance}} = Astro.Ephemeris.moon_ecliptic_position(moment)
    iex> {Float.round(longitude, 4), Float.round(latitude, 4), round(distance)}
    {263.7436, -4.7566, 382267}

# `moon_ecliptic_position_dt`

```elixir
@spec moon_ecliptic_position_dt(float()) ::
  {:ok, {float(), float(), float()}} | {:error, term()}
```

Computes the apparent geocentric ecliptic position of the Moon for the
given dynamical time.

### Arguments

* `dynamical_time` is TDB seconds past J2000.0.

### Returns

* `{:ok, {longitude_deg, latitude_deg, distance_km}}` where longitude
  is in degrees in the range [0, 360) and latitude is in degrees
  in the range [-90, 90].

* `{:error, reason}` if a required ephemeris segment is not found.

### Examples

    iex> {:ok, {longitude, latitude, distance}} = Astro.Ephemeris.moon_ecliptic_position_dt(0.0)
    iex> {Float.round(longitude, 4), Float.round(latitude, 4), round(distance)}
    {223.3149, 5.1709, 402449}

# `moon_position`
*since 2.0.0* 

```elixir
@spec moon_position(DateTime.t()) ::
  {:ok, {right_ascension :: Astro.angle(), float(), float()}} | {:error, term()}
```

Computes the apparent geocentric position of the Moon for the given
datetime.

Chains the Moon/EMB and Earth/EMB segments from the loaded JPL
ephemeris, then applies IAU 1980 precession and nutation to
produce coordinates in the true equator and equinox of date.

### Arguments

* `date_time` is a `DateTime` in any time zone (converted
  internally to a moment and then to dynamical time).

### Returns

* `{:ok, {ra_deg, dec_deg, distance_km}}` where right ascension
  is in degrees in the range [0, 360), declination is in degrees
  in the range [-90, 90], and distance is in kilometers.

* `{:error, reason}` if a required ephemeris segment is not found.

### Examples

    iex> {:ok, {right_ascension, declination, distance}} =
    ...>   Astro.Ephemeris.moon_position(~U[2024-06-21 12:00:00Z])
    iex> {Float.round(right_ascension, 4), Float.round(declination, 4), round(distance)}
    {262.932, -28.0416, 382267}

# `moon_position_dt`

```elixir
@spec moon_position_dt(float()) ::
  {:ok, {float(), float(), float()}} | {:error, term()}
```

Computes the apparent geocentric position of the Moon for the given
dynamical time.

### Arguments

* `dynamical_time` is TDB seconds past J2000.0.

### Returns

* `{:ok, {ra_deg, dec_deg, distance_km}}` where right ascension
  is in degrees in the range [0, 360), declination is in degrees
  in the range [-90, 90], and distance is in kilometers.

* `{:error, reason}` if a required ephemeris segment is not found.

### Examples

    iex> {:ok, {right_ascension, declination, distance}} = Astro.Ephemeris.moon_position_dt(0.0)
    iex> {Float.round(right_ascension, 4), Float.round(declination, 4), round(distance)}
    {222.4436, -10.8979, 402449}

# `moon_position_from_moment`

```elixir
@spec moon_position_from_moment(Astro.Time.moment()) ::
  {:ok, {float(), float(), float()}} | {:error, term()}
```

Computes the apparent geocentric equatorial position of the Moon for
the given moment.

### Arguments

* `moment` is a moment (fractional days since epoch).

### Returns

* `{:ok, {ra_deg, dec_deg, distance_km}}`.

* `{:error, reason}` if a required ephemeris segment is not found.

### Examples

    iex> moment = Astro.Time.date_time_to_moment(~U[2024-06-21 12:00:00Z])
    iex> {:ok, {right_ascension, declination, distance}} = Astro.Ephemeris.moon_position_from_moment(moment)
    iex> {Float.round(right_ascension, 4), Float.round(declination, 4), round(distance)}
    {262.932, -28.0416, 382267}

# `sun_ecliptic_position`

```elixir
@spec sun_ecliptic_position(Astro.Time.moment()) ::
  {:ok, {float(), float(), float()}} | {:error, term()}
```

Computes the apparent geocentric ecliptic position of the Sun for the
given moment.

### Arguments

* `moment` is a moment (fractional days since epoch).

### Returns

* `{:ok, {longitude_deg, latitude_deg, distance_km}}`.

* `{:error, reason}` if a required ephemeris segment is not found.

### Examples

    iex> moment = Astro.Time.date_time_to_moment(~U[2024-06-21 12:00:00Z])
    iex> {:ok, {longitude, latitude, distance}} = Astro.Ephemeris.sun_ecliptic_position(moment)
    iex> {Float.round(longitude, 4), Float.round(latitude, 4), round(distance)}
    {90.6022, -0.0002, 152026542}

# `sun_ecliptic_position_dt`

```elixir
@spec sun_ecliptic_position_dt(float()) ::
  {:ok, {float(), float(), float()}} | {:error, term()}
```

Computes the apparent geocentric ecliptic position of the Sun for the
given dynamical time.

### Arguments

* `dynamical_time` is TDB seconds past J2000.0.

### Returns

* `{:ok, {longitude_deg, latitude_deg, distance_km}}` where longitude
  is in degrees in the range [0, 360) and latitude is in degrees
  in the range [-90, 90].

* `{:error, reason}` if a required ephemeris segment is not found.

### Examples

    iex> {:ok, {longitude, latitude, distance}} = Astro.Ephemeris.sun_ecliptic_position_dt(0.0)
    iex> {Float.round(longitude, 4), Float.round(latitude, 6), round(distance)}
    {280.3682, 2.38e-4, 147103733}

# `sun_position`

```elixir
@spec sun_position(DateTime.t()) ::
  {:ok, {float(), float(), float()}} | {:error, term()}
```

Computes the apparent geocentric position of the Sun for the given
datetime.

Chains the Sun/SSB, EMB/SSB and Earth/EMB segments from the loaded
JPL ephemeris (Sun/SSB − EMB/SSB − Earth/EMB), corrects for light
time and aberration, then applies IAU 1980 precession and nutation.

### Arguments

* `date_time` is a `DateTime` in any time zone.

### Returns

* `{:ok, {ra_deg, dec_deg, distance_km}}` where right ascension
  is in degrees in the range [0, 360), declination is in degrees
  in the range [-90, 90], and distance is in kilometers.

* `{:error, reason}` if a required ephemeris segment is not found.

### Examples

    iex> {:ok, {right_ascension, declination, distance}} =
    ...>   Astro.Ephemeris.sun_position(~U[2024-06-21 12:00:00Z])
    iex> {Float.round(right_ascension, 4), Float.round(declination, 4), round(distance)}
    {90.6564, 23.4368, 152026542}

# `sun_position_dt`

```elixir
@spec sun_position_dt(float()) ::
  {:ok, {float(), float(), float()}} | {:error, term()}
```

Computes the apparent geocentric position of the Sun for the given
dynamical time.

### Arguments

* `dynamical_time` is TDB seconds past J2000.0.

### Returns

* `{:ok, {ra_deg, dec_deg, distance_km}}` where right ascension
  is in degrees in the range [0, 360), declination is in degrees
  in the range [-90, 90], and distance is in kilometers.

* `{:error, reason}` if a required ephemeris segment is not found.

### Examples

    iex> {:ok, {right_ascension, declination, distance}} = Astro.Ephemeris.sun_position_dt(0.0)
    iex> {Float.round(right_ascension, 4), Float.round(declination, 4), round(distance)}
    {281.2776, -23.0325, 147103733}

# `sun_position_from_moment`

```elixir
@spec sun_position_from_moment(Astro.Time.moment()) ::
  {:ok, {float(), float(), float()}} | {:error, term()}
```

Computes the apparent geocentric equatorial position of the Sun for
the given moment.

### Arguments

* `moment` is a moment (fractional days since epoch).

### Returns

* `{:ok, {ra_deg, dec_deg, distance_km}}`.

* `{:error, reason}` if a required ephemeris segment is not found.

### Examples

    iex> moment = Astro.Time.date_time_to_moment(~U[2024-06-21 12:00:00Z])
    iex> {:ok, {right_ascension, declination, distance}} = Astro.Ephemeris.sun_position_from_moment(moment)
    iex> {Float.round(right_ascension, 4), Float.round(declination, 4), round(distance)}
    {90.6564, 23.4368, 152026542}

---

*Consult [api-reference.md](api-reference.md) for complete listing*
