Orbis.GNSS.Data.Catalog (Orbis v0.29.1)

Copy Markdown View Source

Static, pure catalog of GNSS analysis centers and the rules that turn a product specification into a canonical filename and a full archive URL.

Everything here is deterministic and network-free: GPS-week and day-of-year arithmetic, the IGS long-name filename convention, and the per-center archive layout. The fetch pipeline derives all network hosts and cache filenames from this module, which is what keeps the layer safe against SSRF (only known hosts are ever contacted) and path traversal (cache names come only from a validated canonical filename).

Analysis centers

Each center maps to the archive protocol, host, product token it publishes, and directory layout for each content type. Only centers and paths that resolve against a live, anonymous HTTP(S) archive are listed; every entry below has been checked against its server.

CodeCenterProtocolHost
:gfzGFZ Potsdam operational rapidHTTPSisdc-data.gfz.de
:codCODE / University of BernHTTPftp.aiub.unibe.ch
:esaESA Navigation Office final productsHTTPSnavigation-office.esa.int
:igsIGS combined broadcast navigationHTTPSigs.bkg.bund.de
:igs_ultIGS combined ultra-rapidHTTPSigs.bkg.bund.de
:cod_ultCODE ultra-rapidHTTPftp.aiub.unibe.ch
:esa_ultESA ultra-rapidHTTPSnavigation-office.esa.int
:gfz_ultGFZ ultra-rapidHTTPSisdc-data.gfz.de
:cod_rapCODE rapid global ionosphere mapHTTPftp.aiub.unibe.ch
:cod_prd1CODE 1-day predicted ionosphere mapHTTPftp.aiub.unibe.ch
:cod_prd2CODE 2-day predicted ionosphere mapHTTPftp.aiub.unibe.ch

AIUB's public CODE archive does not offer HTTPS. CODE products are public data, but fresh products do not have published checksums; transport integrity therefore relies on the plain-HTTP channel.

Products that were only reachable through the deprecated ESA GSSC anonymous archive and have no verified open HTTP(S) mirror are intentionally not listed. Requests for those former products return {:error, {:no_open_mirror, {center, content}}}.

Content types and what each center serves

  • :sp3, :clk — precise orbits and clocks. :gfz (operational rapid), :cod and :esa (final products), and the ultra-rapid center aliases :igs_ult, :cod_ult, :esa_ult, and :gfz_ult. The GFZ rapid token is GFZ0OPSRAP; the CODE MGEX final token is COD0MGXFIN; the ESA final token is ESA0MGNFIN; the ultra-rapid tokens are IGS0OPSULT, COD0OPSULT, ESA0OPSULT, and GFZ0OPSULT.
  • :nav — the IGS merged multi-GNSS broadcast navigation file (BRDC00WRD_R_..._MN.rnx). Only :igs publishes it.
  • :ionex — the global ionosphere TEC map (..._GIM.INX). :cod serves the final COD0OPSFIN over HTTP and :esa serves ESA0OPSFIN over HTTPS. Lower-latency CODE GIMs live in the AIUB /CODE root: :cod_rap serves the rapid map COD0OPSRAP (short name CORG<ddd>0.<yy>I), and the predicted aliases :cod_prd1 (1-day-ahead) and :cod_prd2 (2-day-ahead) both serve COD0OPSPRD (short name COPG<ddd>0.<yy>I). The predicted product is published in real time: the 1-day map for a UTC day exists before that day starts, so :cod_prd1 resolves for the current/near-future UTC day and :cod_prd2 for the day after. AIUB encodes the prediction horizon only in the file's COMMENT header, not the filename; the catalog distinguishes the horizons by the target date the convenience builders offset to. The IGS combined rapid map IGS0OPSRAP has no verified open HTTP(S) mirror and stays in @no_open_mirrors. IONEX cadence is sub-daily, so the default sampling is 01H/02H, not 01D.

Filename conventions

Precise products and IONEX follow the IGS long-name convention AAAVPPPTTT_YYYYDDDHHMM_LEN_SMP_CNT.EXT (e.g. GFZ0OPSRAP_20201760000_01D_15M_ORB.SP3). Ultra-rapid SP3 uses the same form with a sub-daily issue time and a two-day span, e.g. IGS0OPSULT_20242470600_02D_15M_ORB.SP3. CODE ultra-rapid SP3 on AIUB is published as a daily, uncompressed 01D file. Broadcast navigation uses the RINEX long-name SSSSMRCCC_R_YYYYDDDHHMM_LEN_CNT.fmt with no sampling field and a lowercase extension (e.g. BRDC00WRD_R_20201770000_01D_MN.rnx).

Summary

Functions

The set of hosts the layer is permitted to contact.

Build the full, compressed (.gz) archive URL for a product.

Build the full, compressed (.gz) archive URL for a product with an optional issue time.

Build the canonical IGS long-name filename for a product.

Build the canonical filename for a product with an optional sub-daily issue.

Look up a center's static definition.

Human-readable center name, or nil if the code is unknown.

All supported analysis-center codes.

The archive compression used for a center/content pair.

The content-type descriptor (%{code:, ext:, kind:}) for a content type.

All supported content-type codes.

The day-of-year (001366) for a calendar date.

The catalog default sampling code for a center/content pair, when one is known.

Candidate UTC dates for a daily GIM (rapid or predicted) at or before target, newest first.

The GPS day-of-week for a calendar date (0 = Sunday … 6 = Saturday).

The GPS week number for a calendar date.

Resolve the latest available ultra-rapid issue at or before target.

Day offset, relative to a target UTC date, that a predicted IONEX alias maps to.

The transfer protocol (:https or :http) for a center.

Build the canonical IGS long-name filename for a daily station observation product (RINEX 3 CRINEX), e.g. ESBC00DNK_R_20201770000_01D_30S_MO.crx.

The transfer protocol for the daily station observation archive.

Build the full, compressed (.gz) archive URL for a daily station observation product on the public BKG IGS observation tree.

Return candidate ultra-rapid issues at or before a target epoch, newest first.

Types

compression()

@type compression() :: :gzip | :none

error()

@type error() ::
  {:error, {:unsupported_product, term()}}
  | {:error, {:no_open_mirror, {atom(), atom()}}}

Functions

allowed_hosts()

@spec allowed_hosts() :: MapSet.t(String.t())

The set of hosts the layer is permitted to contact.

Used by the download path as an allow-list so a malformed or unexpected URL can never cause a request to an off-catalog host.

archive_url(center, content, date, sample)

@spec archive_url(atom(), atom(), Date.t(), String.t()) :: {:ok, String.t()} | error()

Build the full, compressed (.gz) archive URL for a product.

The directory follows the center/content layout; the filename is the canonical long-name plus a .gz suffix. The host is always one of the catalog hosts, never caller-supplied input.

Returns {:ok, url} or a tagged error tuple.

Examples

iex> Orbis.GNSS.Data.Catalog.archive_url(:gfz, :sp3, ~D[2020-06-24], "15M")
{:ok, "https://isdc-data.gfz.de/gnss/products/rapid/w2111/GFZ0OPSRAP_20201760000_01D_15M_ORB.SP3.gz"}

iex> Orbis.GNSS.Data.Catalog.archive_url(:igs, :nav, ~D[2020-06-25], "01D")
{:ok, "https://igs.bkg.bund.de/root_ftp/IGS/BRDC/2020/177/BRDC00WRD_R_20201770000_01D_MN.rnx.gz"}

archive_url(center, content, date, sample, issue)

@spec archive_url(atom(), atom(), Date.t(), String.t(), String.t() | nil) ::
  {:ok, String.t()} | error()

Build the full, compressed (.gz) archive URL for a product with an optional issue time.

Examples

iex> Orbis.GNSS.Data.Catalog.archive_url(:cod_ult, :sp3, ~D[2026-06-11], "05M", "0000")
{:ok, "http://ftp.aiub.unibe.ch/CODE/COD0OPSULT_20261620000_01D_05M_ORB.SP3"}

canonical_filename(center, content, date, sample)

@spec canonical_filename(atom(), atom(), Date.t(), String.t()) ::
  {:ok, String.t()} | error()

Build the canonical IGS long-name filename for a product.

Precise products and IONEX use AAAVPPPTTT_YYYYDDDHHMM_LEN_SMP_CNT.EXT; broadcast navigation uses the no-sampling RINEX form SSSSMRCCC_R_YYYYDDDHHMM_LEN_CNT.ext. The center must actually publish the requested content type.

Returns {:ok, filename} or a tagged error tuple.

Examples

iex> Orbis.GNSS.Data.Catalog.canonical_filename(:gfz, :sp3, ~D[2020-06-24], "15M")
{:ok, "GFZ0OPSRAP_20201760000_01D_15M_ORB.SP3"}

iex> Orbis.GNSS.Data.Catalog.canonical_filename(:igs, :nav, ~D[2020-06-25], "01D")
{:ok, "BRDC00WRD_R_20201770000_01D_MN.rnx"}

canonical_filename(center, content, date, sample, issue)

@spec canonical_filename(atom(), atom(), Date.t(), String.t(), String.t() | nil) ::
  {:ok, String.t()} | error()

Build the canonical filename for a product with an optional sub-daily issue.

issue is nil for daily products and an HHMM string for ultra-rapid products. Ultra-rapid centers reject unsupported issue times instead of silently rounding.

Examples

iex> Orbis.GNSS.Data.Catalog.canonical_filename(:igs_ult, :sp3, ~D[2024-09-03], "15M", "0600")
{:ok, "IGS0OPSULT_20242470600_02D_15M_ORB.SP3"}

center(code)

@spec center(atom()) :: {:ok, map()} | {:error, {:unsupported_product, term()}}

Look up a center's static definition.

Returns {:ok, map} or {:error, {:unsupported_product, {:center, code}}}.

center_name(code)

@spec center_name(atom()) :: String.t() | nil

Human-readable center name, or nil if the code is unknown.

centers()

@spec centers() :: [atom()]

All supported analysis-center codes.

compression(center, content)

@spec compression(atom(), atom()) :: {:ok, compression()} | error()

The archive compression used for a center/content pair.

content(type)

@spec content(atom()) :: {:ok, map()} | {:error, {:unsupported_product, term()}}

The content-type descriptor (%{code:, ext:, kind:}) for a content type.

Returns {:ok, map} or {:error, {:unsupported_product, {:content, type}}}.

content_types()

@spec content_types() :: [atom()]

All supported content-type codes.

day_of_year(date)

@spec day_of_year(Date.t()) :: 1..366

The day-of-year (001366) for a calendar date.

Examples

iex> Orbis.GNSS.Data.Catalog.day_of_year(~D[2020-06-24])
176

default_sample(center, content)

@spec default_sample(atom(), atom()) :: {:ok, String.t()} | error()

The catalog default sampling code for a center/content pair, when one is known.

Ultra-rapid centers publish different native orbit cadences (IGS/ESA at 15 minutes, several analysis-center products at 5 minutes), so callers should prefer this over a global default when building live-latency products.

Examples

iex> Orbis.GNSS.Data.Catalog.default_sample(:igs_ult, :sp3)
{:ok, "15M"}

iex> Orbis.GNSS.Data.Catalog.default_sample(:gfz_ult, :sp3)
{:ok, "05M"}

gim_date_candidates(center, target, lookback \\ 2)

@spec gim_date_candidates(
  atom(),
  Date.t() | NaiveDateTime.t() | DateTime.t(),
  non_neg_integer()
) :: [Date.t()] | error()

Candidate UTC dates for a daily GIM (rapid or predicted) at or before target, newest first.

Unlike ultra-rapid SP3, the CODE rapid and predicted GIMs are daily files with no sub-daily issue time, so the latest-available fallback walks the calendar day backward instead of the issue clock. The rapid map lands a day or two late and the predicted map is published ahead of its target day; in both cases the freshest file present may be for a slightly earlier day than first requested, so the fetch layer tries these candidates newest first.

Returns a list of Date values (at most lookback + 1 entries, default lookback 2), or a tagged error for an unsupported center/content.

Examples

iex> Orbis.GNSS.Data.Catalog.gim_date_candidates(:cod_rap, ~D[2026-06-14])
[~D[2026-06-14], ~D[2026-06-13], ~D[2026-06-12]]

iex> Orbis.GNSS.Data.Catalog.gim_date_candidates(:cod_prd1, ~D[2026-06-14], 1)
[~D[2026-06-14], ~D[2026-06-13]]

gps_day_of_week(date)

@spec gps_day_of_week(Date.t()) :: 0..6

The GPS day-of-week for a calendar date (0 = Sunday … 6 = Saturday).

Examples

iex> Orbis.GNSS.Data.Catalog.gps_day_of_week(~D[2020-06-24])
3

gps_week(date)

@spec gps_week(Date.t()) :: non_neg_integer()

The GPS week number for a calendar date.

GPS week 0 began on 1980-01-06. Uses exact integer day arithmetic, so it is leap-second-agnostic (week numbering is a calendar count, not a clock count).

Examples

iex> Orbis.GNSS.Data.Catalog.gps_week(~D[2020-06-24])
2111

latest_ultra_issue(center, target, available \\ nil)

@spec latest_ultra_issue(
  atom(),
  NaiveDateTime.t() | DateTime.t(),
  nil | [%{date: Date.t(), issue: String.t()} | {Date.t(), String.t()}]
) :: {:ok, %{date: Date.t(), issue: String.t()}} | error()

Resolve the latest available ultra-rapid issue at or before target.

available is optional. When provided, it is a list of %{date:, issue:} maps or {date, issue} tuples representing archive entries known to exist; the resolver picks the newest candidate present in that set. This keeps network probing outside the pure catalog while letting the fetch layer fall back from a missing latest issue to an older one.

Examples

iex> available = [{~D[2024-09-03], "0000"}, {~D[2024-09-03], "0600"}]
iex> Orbis.GNSS.Data.Catalog.latest_ultra_issue(:igs_ult, ~N[2024-09-03 13:00:00], available)
{:ok, %{date: ~D[2024-09-03], issue: "0600"}}

predicted_day_offset(arg1)

@spec predicted_day_offset(atom()) :: integer()

Day offset, relative to a target UTC date, that a predicted IONEX alias maps to.

CODE publishes a single predicted product (COD0OPSPRD); the prediction horizon ("1-DAY", "2-DAY", ... PREDICTED) appears only in the file's COMMENT header, never the filename. The catalog therefore distinguishes the horizons by the calendar day each alias targets: :cod_prd1 is the current/near-future day (offset 0) and :cod_prd2 is the day after (offset +1). Non-predicted centers return 0.

Examples

iex> Orbis.GNSS.Data.Catalog.predicted_day_offset(:cod_prd1)
0

iex> Orbis.GNSS.Data.Catalog.predicted_day_offset(:cod_prd2)
1

iex> Orbis.GNSS.Data.Catalog.predicted_day_offset(:cod_rap)
0

protocol(center)

@spec protocol(atom()) ::
  {:ok, :https | :http} | {:error, {:unsupported_product, term()}}

The transfer protocol (:https or :http) for a center.

station_obs_filename(station, date, sample)

@spec station_obs_filename(String.t(), Date.t(), String.t()) ::
  {:ok, String.t()} | {:error, {:unsupported_product, term()}}

Build the canonical IGS long-name filename for a daily station observation product (RINEX 3 CRINEX), e.g. ESBC00DNK_R_20201770000_01D_30S_MO.crx.

Station observation files are keyed by a 9-character site id, not an analysis-center token, so they have their own builder.

Examples

iex> Orbis.GNSS.Data.Catalog.station_obs_filename("ESBC00DNK", ~D[2020-06-25], "30S")
{:ok, "ESBC00DNK_R_20201770000_01D_30S_MO.crx"}

station_obs_protocol()

@spec station_obs_protocol() :: :https

The transfer protocol for the daily station observation archive.

station_obs_url(station, date, sample)

@spec station_obs_url(String.t(), Date.t(), String.t()) ::
  {:ok, String.t()} | {:error, {:unsupported_product, term()}}

Build the full, compressed (.gz) archive URL for a daily station observation product on the public BKG IGS observation tree.

Examples

iex> Orbis.GNSS.Data.Catalog.station_obs_url("WTZR00DEU", ~D[2020-06-25], "30S")
{:ok, "https://igs.bkg.bund.de/root_ftp/IGS/obs/2020/177/WTZR00DEU_R_20201770000_01D_30S_MO.crx.gz"}

ultra_issue_candidates(center, target)

@spec ultra_issue_candidates(atom(), NaiveDateTime.t() | DateTime.t()) ::
  [%{date: Date.t(), issue: String.t()}] | error()

Return candidate ultra-rapid issues at or before a target epoch, newest first.

The returned entries are maps with :date and :issue keys. A previous-day issue is included when it is the newest product not after the target; this is required for early UTC hours when the current-day file has not landed yet.

Examples

iex> Orbis.GNSS.Data.Catalog.ultra_issue_candidates(:igs_ult, ~N[2024-09-03 13:15:00]) |> Enum.take(3)
[%{date: ~D[2024-09-03], issue: "1200"}, %{date: ~D[2024-09-03], issue: "0600"}, %{date: ~D[2024-09-03], issue: "0000"}]