# `Sidereon.GNSS.PreciseEphemeris`
[🔗](https://github.com/neilberkman/sidereon-ex/blob/main/lib/sidereon/gnss/precise_ephemeris.ex#L1)

A precise-ephemeris source built directly from samples, with no SP3 text in the
loop.

This is the Elixir surface over `sidereon-core`'s sample-backed precise-ephemeris
source. The canonical intermediate representation of a precise orbit/clock
product is a set of per-satellite ECEF position (+ optional clock) samples on a
time axis (`Sidereon.GNSS.PreciseEphemerisSample`); this module builds an
interpolatable source from those samples directly. It drives the exact same
interpolation substrate the SP3-parsed source uses, so
`Sidereon.GNSS.Observables.predict_ranges/3` accepts either kind of source.

A built source is held as a resource handle by the BEAM; evaluation operates on
that handle.

## Round trip

    {:ok, sp3} = Sidereon.GNSS.SP3.load("igs.sp3")
    samples = Sidereon.GNSS.SP3.precise_ephemeris_samples(sp3)
    {:ok, source} = Sidereon.GNSS.PreciseEphemeris.from_samples(samples)

For samples that are the faithful image of the interpolation fit nodes (the
round-trip case above), the rebuilt source interpolates and predicts ranges
byte-identically to the SP3-parsed source. Samples carrying lower precision
interpolate at that precision.

# `t`

```elixir
@type t() :: %Sidereon.GNSS.PreciseEphemeris{
  handle: reference(),
  time_scale: String.t() | nil
}
```

# `from_samples`

```elixir
@spec from_samples([Sidereon.GNSS.PreciseEphemerisSample.t()]) ::
  {:ok, t()} | {:error, term()}
```

Build a precise-ephemeris source from a list of
`Sidereon.GNSS.PreciseEphemerisSample` structs.

Samples are grouped by satellite. Each satellite's series must be strictly
increasing in epoch and carry at least two samples, and every sample must share
one time scale. Returns `{:ok, %Sidereon.GNSS.PreciseEphemeris{}}`, or
`{:error, reason}` where `reason` is one of the structural validation atoms:

  * `:empty` - no samples supplied
  * `:single_sample_satellite` - a satellite has only one sample
  * `:non_monotonic` - a satellite's epochs are not strictly increasing
  * `:mixed_timescale` - samples carry more than one time scale
  * `:non_finite` - a sample position or clock value was not finite
  * `:out_of_range` - a sample epoch is not representable as J2000 seconds

A malformed satellite token or time scale in a sample is returned verbatim as
`{:error, reason}` without raising.

# `observable_states_at_j2000_s`

```elixir
@spec observable_states_at_j2000_s(t(), [String.t()], [number()]) ::
  {:ok, Sidereon.GNSS.PreciseEphemeris.StateBatch.t()} | {:error, term()}
```

Evaluate states for parallel satellite and J2000-second arrays.

# `observable_states_at_shared_j2000_s`

```elixir
@spec observable_states_at_shared_j2000_s(t(), [String.t()], number()) ::
  {:ok, Sidereon.GNSS.PreciseEphemeris.StateBatch.t()} | {:error, term()}
```

Evaluate states for many satellites at one shared J2000-second epoch.

# `satellites`

```elixir
@spec satellites(t()) :: [String.t()] | {:error, term()}
```

Return the satellite ids available in this sample-backed precise source.

---

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