# `Sidereon.Atmosphere`
[🔗](https://github.com/neilberkman/sidereon-ex/blob/main/lib/sidereon/atmosphere.ex#L1)

Atmospheric density via the NRLMSISE-00 empirical model.

NRLMSISE-00 computes total mass density and temperature from the surface
to the lower exosphere (~1000 km). It is the standard model used for
satellite drag prediction.

The reported `density` is the drag-effective total mass density (`gtd7d`):
anomalous oxygen is folded in, which matters above ~500 km. This is the
correct quantity for drag users.

## Valid domain

Altitude must be in `[0, 1000]` km (the model's validity range); `:f107`,
`:f107a`, and `:ap` must be finite and non-negative. Inputs outside this
domain return a tagged error rather than an extrapolated result.

## Quick example

    position = %{latitude: 0.0, longitude: 0.0, altitude_km: 400.0}
    {:ok, result} = Sidereon.Atmosphere.density(position, ~U[2024-06-20 12:00:00Z])
    result.density    # kg/m^3 (e.g., ~3e-12 at ISS altitude)
    result.temperature # K

## Solar/geomagnetic indices

The model depends on space weather conditions. You can supply these via
the `:f107`, `:f107a`, and `:ap` options. If omitted, moderate defaults
are used (F10.7 = F10.7A = 150.0, Ap = 4.0).

For operational use, fetch current indices from NOAA/SWPC.

# `datetime`

```elixir
@type datetime() ::
  DateTime.t()
  | {{integer(), integer(), integer()}, {integer(), integer(), number()}}
```

# `density_error`

```elixir
@type density_error() ::
  {:missing_field, atom()}
  | {:invalid_field, atom(), term()}
  | {:invalid_option, atom()}
  | {:invalid_datetime, term()}
  | {:nif_error, term()}
```

# `position`

```elixir
@type position() :: %{latitude: number(), longitude: number(), altitude_km: number()}
```

# `result`

```elixir
@type result() :: %{density: float(), temperature: float()}
```

# `density`

```elixir
@spec density(term(), term(), keyword()) ::
  {:ok, result()} | {:error, density_error()}
```

Compute atmospheric density and temperature at a given position and time.

## Parameters

  - `position` - geodetic position as `%{latitude: deg, longitude: deg, altitude_km: km}`
  - `datetime` - observation time as `DateTime` or `{{y,m,d},{h,m,s}}` tuple
  - `opts` - keyword list of optional space weather indices:
    - `:f107` - daily 10.7 cm solar radio flux (default 150.0)
    - `:f107a` - 81-day average of F10.7 (default 150.0)
    - `:ap` - daily geomagnetic Ap index (default 4.0)

## Returns

`{:ok, %{density: kg_per_m3, temperature: kelvin}}` or `{:error, reason}`.

## Examples

    # ISS altitude with default solar activity
    pos = %{latitude: 28.5, longitude: -80.6, altitude_km: 408.0}
    {:ok, result} = Sidereon.Atmosphere.density(pos, ~U[2024-06-20 12:00:00Z])

    # With explicit solar indices
    {:ok, result} = Sidereon.Atmosphere.density(pos, ~U[2024-06-20 12:00:00Z],
      f107: 180.0, f107a: 160.0, ap: 15.0)

---

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