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

Angular geometry calculations for satellites.

Computes angular separations between a satellite and celestial bodies
(Sun, Moon) using vector geometry in the GCRS frame. Useful for:

- Solar panel pointing analysis
- Lunar interference assessment
- Optical brightness estimation (phase angle)
- Eclipse geometry (Earth angular radius)

All positions are expected in km in the GCRS (J2000/ICRF) frame.
All returned angles are in degrees.

## Example

    {:ok, eph} = Sidereon.Ephemeris.load("de421.bsp")
    {:ok, tle} = Sidereon.parse_tle(line1, line2)
    result = Sidereon.Angles.compute(tle, ~U[2024-06-21 12:00:00Z], eph)
    result.sun_angle      # degrees
    result.moon_angle     # degrees
    result.sun_elevation  # degrees (positive = sunlit side)
    result.earth_angle    # degrees (angular radius of Earth)

# `angular_separation`

```elixir
@spec angular_separation(
  {number(), number(), number()},
  {number(), number(), number()}
) :: float()
```

Angular separation between two non-zero vectors, in degrees.

# `angular_separation_coords`

```elixir
@spec angular_separation_coords({number(), number()}, {number(), number()}) :: float()
```

Angular separation between two `{longitude_deg, latitude_deg}` pairs.

# `beta_angle`

```elixir
@spec beta_angle({number(), number(), number()}, {number(), number(), number()}) ::
  float()
```

Solar beta angle for an orbit normal and Sun vector, in degrees.

# `beta_angle_from_state`

```elixir
@spec beta_angle_from_state(
  {number(), number(), number()},
  {number(), number(), number()},
  {number(), number(), number()}
) :: float()
```

Solar beta angle computed from position, velocity, and Sun vector, in degrees.

# `compute`

```elixir
@spec compute(Sidereon.Elements.t(), DateTime.t(), Sidereon.Ephemeris.t()) ::
  {:ok, map()} | {:error, term()}
```

Compute all standard angles for a satellite at a given time.

Propagates the TLE, gets Sun and Moon positions from the ephemeris,
and returns a map of angles.

## Parameters

  - `tle` - parsed `%Sidereon.Elements{}` struct
  - `datetime` - `DateTime.t()` observation time
  - `ephemeris` - loaded `%Sidereon.Ephemeris{}` handle

## Returns

    %{
      sun_angle: float(),       # nadir-to-Sun angle in degrees
      moon_angle: float(),      # nadir-to-Moon angle in degrees
      sun_elevation: float(),   # Sun elevation above local horizontal
      earth_angle: float()      # Earth angular radius from satellite
    }

# `earth_angular_radius`

```elixir
@spec earth_angular_radius({number(), number(), number()}) :: float()
```

Angular radius of the Earth as seen from the satellite.

This is the half-angle of the cone that just encloses the Earth's disk
as seen from the satellite's position: `asin(R_earth / |sat_position|)`.

Useful for eclipse geometry: if the Sun is within this angular radius
of the anti-nadir direction, the satellite may be in Earth's shadow.

## Parameters

  - `satellite_gcrs_position` - `{x, y, z}` satellite position in GCRS (km)

Returns angular radius in degrees.

# `moon_angle`

```elixir
@spec moon_angle(
  {number(), number(), number()},
  {number(), number(), number()}
) :: float()
```

Angle between satellite nadir (toward Earth) and the Moon direction.

## Parameters

  - `satellite_gcrs_position` - `{x, y, z}` satellite position in GCRS (km)
  - `moon_position_from_earth` - `{x, y, z}` Moon position relative to Earth (km)

Returns angle in degrees.

# `phase_angle`

```elixir
@spec phase_angle(
  {number(), number(), number()},
  {number(), number(), number()},
  {number(), number(), number()}
) :: float()
```

Sun-satellite-observer phase angle.

The phase angle is the angle at the satellite between the Sun and the
observer. It determines the illumination geometry for optical brightness
estimation:

- 0 deg = full phase (Sun behind observer, satellite fully lit)
- 180 deg = new phase (Sun behind satellite, satellite in shadow from observer)

## Parameters

  - `satellite_gcrs_position` - `{x, y, z}` satellite position in GCRS (km)
  - `sun_position_from_earth` - `{x, y, z}` Sun position relative to Earth (km)
  - `observer_position` - `{x, y, z}` observer position in GCRS (km)

Returns phase angle in degrees (0 to 180).

# `position_angle`

```elixir
@spec position_angle({number(), number()}, {number(), number()}) :: float()
```

Position angle from the first `{longitude_deg, latitude_deg}` coordinate to the second, in degrees.

# `sun_angle`

```elixir
@spec sun_angle(
  {number(), number(), number()},
  {number(), number(), number()}
) :: float()
```

Angle between satellite nadir (toward Earth) and the Sun direction.

The nadir vector points from the satellite toward Earth's center,
i.e., it is the negation of the satellite's GCRS position.

## Parameters

  - `satellite_gcrs_position` - `{x, y, z}` satellite position in GCRS (km)
  - `sun_position_from_earth` - `{x, y, z}` Sun position relative to Earth (km)

Returns angle in degrees (0 = Sun is directly below satellite toward Earth,
180 = Sun is directly above/away from Earth).

# `sun_elevation`

```elixir
@spec sun_elevation(
  {number(), number(), number()},
  {number(), number(), number()}
) :: float()
```

Sun elevation above or below the satellite's local horizontal plane.

The local horizontal plane is perpendicular to the radial (nadir) vector.
Positive elevation means the Sun is on the sunlit (away from Earth) side;
negative means it is on the shadow (Earth) side.

## Parameters

  - `satellite_gcrs_position` - `{x, y, z}` satellite position in GCRS (km)
  - `sun_position_from_earth` - `{x, y, z}` Sun position relative to Earth (km)

Returns elevation in degrees (-90 to +90).

---

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