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

Cache-first acquisition for GNSS products and DTED terrain tiles.

This module performs transport, cache IO, checksum verification, and
provenance recording. Product names, archive URLs, terrain cache paths, and HGT
to DTED conversion are delegated to the core NIF, so cache files produced by
another binding under the same root are verified by the same relative path and
data-file digest.

Numeric modules do not fetch data. Fetch first, then pass the cached file or
terrain root to the reader or solver.

## Quick Start

    {:ok, tile_path} = Sidereon.GNSS.Data.fetch_dted(36.75, -106.25)
    terrain_root = Path.dirname(Path.dirname(tile_path))
    {:ok, terrain} = Sidereon.Terrain.dted(terrain_root)
    {:ok, height_m} = Sidereon.Terrain.height(terrain, -106.25, 36.75)

## Bulk Terrain Workflow

    {:ok, report} =
      Sidereon.GNSS.Data.prefetch_dted_bbox({36.0, -107.0, 37.0, -106.0},
        cache_dir: "/tmp/sidereon-terrain"
      )

    {:ok, terrain} = Sidereon.Terrain.dted("/tmp/sidereon-terrain")
    {:ok, _height_m} = Sidereon.Terrain.height(terrain, -106.5, 36.5)

C and WASM consumers use the pure core derivation and conversion functions
directly and provide their own transport and cache policy.

# `error_reason`

```elixir
@type error_reason() ::
  :offline_cache_miss
  | {:not_found_on_archive, String.t()}
  | {:http_status, integer(), String.t()}
  | {:redirect_not_allowed, integer(), String.t()}
  | {:network, term()}
  | {:checksum_mismatch, String.t(), String.t()}
  | {:download_size_exceeded, non_neg_integer()}
  | {:decompress, term()}
  | {:cache_not_writable, term()}
  | {:unknown_center, String.t()}
  | {:unsupported_product, term()}
  | {:invalid_coordinate, number(), number()}
  | {:invalid_tile_index, integer(), integer()}
  | {:invalid_tile_id, String.t()}
  | {:incompatible_sources, [String.t()], term()}
  | {:no_products, [term()]}
  | {:no_coverage, String.t()}
  | {:unknown_product, term()}
```

# `acquire`

Acquire an exact GNSS product and return its path plus public provenance.

# `allowed_hosts`

```elixir
@spec allowed_hosts() :: [String.t()]
```

Archive hosts allowed by the core catalog.

# `archive_url`

```elixir
@spec archive_url(Sidereon.GNSS.Data.Product.t()) ::
  {:ok, String.t()} | {:error, error_reason()}
```

Full archive URL for a product.

# `archive_url`

# `canonical_filename`

```elixir
@spec canonical_filename(Sidereon.GNSS.Data.Product.t()) ::
  {:ok, String.t()} | {:error, error_reason()}
```

Canonical archive filename for a product.

# `canonical_filename`

# `cddis_url`

Build the official NASA CDDIS URL for an exact SP3 or IONEX identity.

# `centers`

```elixir
@spec centers() :: [String.t()]
```

Supported analysis-center codes.

# `content_types`

```elixir
@spec content_types() :: [String.t()]
```

Supported GNSS product type codes.

# `day_of_year`

```elixir
@spec day_of_year(Date.t() | NaiveDateTime.t() | tuple()) ::
  {:ok, non_neg_integer()} | {:error, error_reason()}
```

Day-of-year for a date.

# `default_cache_dir`

```elixir
@spec default_cache_dir() :: String.t()
```

Default GNSS cache root.

# `default_cache_dir`

```elixir
@spec default_cache_dir(:gnss | :terrain) :: String.t()
```

Default cache root for `:gnss` or `:terrain`.

# `default_sample`

```elixir
@spec default_sample(term(), term()) :: {:ok, String.t()} | {:error, error_reason()}
```

Return the catalog's compatibility sampling default without a product date.

# `default_sample_for_date`

```elixir
@spec default_sample_for_date(term(), term(), Date.t() | NaiveDateTime.t() | tuple()) ::
  {:ok, String.t()} | {:error, error_reason()}
```

Return the catalog sampling default for an exact product date.

# `dted_block_dir`

Derive the DTED ten-degree block directory.

# `dted_cache_relpath`

Derive the DTED cache relative path.

# `dted_tile_filename`

Derive the DTED tile filename.

# `fetch`

```elixir
@spec fetch(
  Sidereon.GNSS.Data.Product.t(),
  keyword()
) :: {:ok, String.t()} | {:error, error_reason()}
```

Fetch a GNSS product and return the verified local file path.

# `fetch_dted`

Fetch the DTED tile covering `lat_deg`, `lon_deg`.

# `fetch_ionex`

Fetch the newest available IONEX candidate for a target date.

# `fetch_merged_sp3`

Fetch SP3 products from several centers and merge the contributors.

Ultra-rapid centers try only officially cataloged dated variants after an
archive miss. A second candidate exists only for an explicitly evidenced
publication overlap. Each successful contributor records the satisfying
`:pattern` in the report.

All `Sidereon.GNSS.SP3.merge/2` options are forwarded, including `:combine`,
`:min_agree`, `:precedence_scope`, and `:outlier_reject`. A single successful
contributor also passes through `SP3.merge/2`, so filters, target cadence, and
validation behave consistently; its report has `single_product: true` and
`merged: true`.

# `fetch_merged_sp3_file`

Fetch merged SP3 and write it to a file.

# `fetch_merged_sp3_file_with_report`

Fetch and write merged SP3 while returning the complete acquisition report.

# `fetch_space_weather`

```elixir
@spec fetch_space_weather(keyword()) ::
  {:ok, Sidereon.SpaceWeather.t()} | {:error, error_reason()}
```

Fetch, verify, cache, and parse a space-weather product.

# `gps_week`

```elixir
@spec gps_week(Date.t() | NaiveDateTime.t() | tuple()) ::
  {:ok, non_neg_integer()} | {:error, error_reason()}
```

GPS week number for a date.

# `identity`

Resolve an exact GNSS product identity independently from its distributor.

# `merge_report_to_map`

Convert a merged-SP3 report to a secret-free persistence map.

The map never includes credentials, authorization headers, cache paths, or
temporary paths. Recomputing `stable_input_identity` requires only the
contributor artifact identities and `merge_policy` in this map.

# `mgex_clk`

Build a RINEX clock product.

# `mgex_ionex`

Build an IONEX product.

# `mgex_nav`

Build a merged broadcast-navigation product.

# `mgex_sp3`

Build an SP3 product.

# `ops_ultra_sp3`

Build an ultra-rapid OPS SP3 product.

When `:sample` is omitted, the core catalog selects the published cadence for
the exact issue, including intraday cadence transitions.

# `parse_skadi_tile_id`

Parse a Skadi tile id.

# `populate_terrain_cache`

Populate a terrain cache from a bbox tuple or tile list.

# `predicted_ionex`

Build a predicted IONEX product.

# `prefetch_dted_bbox`

Prefetch all terrain tiles in an inclusive bounding box.

# `prefetch_dted_tiles`

Prefetch an explicit list of terrain tile indices or Skadi tile ids.

# `product`

```elixir
@spec product(term(), term(), Date.t() | NaiveDateTime.t() | tuple(), keyword()) ::
  {:ok, Sidereon.GNSS.Data.Product.t()} | {:error, error_reason()}
```

Build a product specification for any supported center/product/date.

# `product_solution_class`

```elixir
@spec product_solution_class(term(), term()) ::
  {:ok, String.t()} | {:error, error_reason()}
```

Return the solution class for a specific center/product family.

# `rapid_ionex`

Build the rapid IONEX product for a date.

# `request`

Build an exact GNSS request with an ordered caller-controlled source list.

# `skadi_archive_url`

Derive a Skadi archive URL.

# `skadi_band`

Derive a Skadi latitude band.

# `skadi_tile_id`

Derive a Skadi tile id.

# `sp3_content_start_convention`

```elixir
@spec sp3_content_start_convention(
  term(),
  Date.t() | NaiveDateTime.t() | tuple(),
  term() | nil
) ::
  {:ok, Sidereon.GNSS.Data.Sp3ContentStartConvention.t()}
  | {:error, error_reason()}
```

Return the cataloged first-content convention for one exact SP3 issue.

Ultra-rapid centers require an official `HHMM` issue. Product lines without
issue times require `nil`. The result comes directly from the core catalog,
including historical publication transitions.

# `space_weather_archive_url`

Derive the space-weather archive URL.

# `space_weather_cache_relpath`

Derive the space-weather cache relative path.

# `space_weather_filename`

Derive a space-weather filename.

# `supported_samples`

```elixir
@spec supported_samples(
  term(),
  term(),
  Date.t() | NaiveDateTime.t() | tuple(),
  term() | nil
) :: {:ok, [String.t()]} | {:error, error_reason()}
```

Return officially cataloged sampling tokens for one product date and issue.

The result is product-, date-, and issue-aware. For an issue-based product
line, omitting `issue` queries the `0000` issue; product construction still
requires an explicit issue. Unsupported centers, products, eras, and issue
values return `{:error, {:unsupported_product, reason}}`.

# `terrain_tile_index`

Derive the terrain tile index covering a coordinate.

# `validate_exact_product_set`

Require available identities to be exactly the declared product set.

# `verify_merge_report`

```elixir
@spec verify_merge_report(Sidereon.GNSS.Data.MergeReport.t() | map()) ::
  :ok | {:error, term()}
```

Verify a merged-SP3 report or a decoded persistence map.

This reconstructs no filename and reads no cache directory. It validates the
complete persisted artifact records and merge policy through the shared Rust
canonicalizer, then compares both the schema version and stable identity. All
report and nested record schemas are exact: unknown, duplicated, coercive, or
internally inconsistent fields fail closed.
Maps returned by `merge_report_to_map/1`, including a JSON encode/decode
round-trip with string keys, are accepted.

# `write_sp3`

Write an SP3 product atomically.

---

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