Public GNSS distribution sources
July 21, 2026 ยท View on GitHub
Sidereon treats a GNSS product and the place used to obtain it as separate public concepts. An exact request fixes the publisher, analysis-center product line, solution class, issue, cadence, coverage date, official filename, and format. Selecting another distributor never changes those fields.
The exact-acquisition API is available from sidereon.distribution and is
also lazily re-exported by sidereon.data.
from datetime import date
import os
from sidereon import data
product = data.mgex_sp3("cod", date(2026, 6, 25))
request = data.request(
product,
[
data.Distribution.nasa_cddis(),
data.Distribution.direct(),
],
)
result = data.acquire(
request,
earthdata_auth=data.EarthdataAuth.bearer(os.environ["EARTHDATA_TOKEN"]),
)
print(result.path)
print(result.provenance.distribution_source)
print(result.provenance.sha256)
Only the listed distributors are attempted, in order. A 404 from CDDIS may
therefore lead to the direct archive in this example, but both attempts use the
same ProductIdentity. Sidereon does not change center, tier, issue, cadence,
date, product family, or official filename. Earlier public failures are retained
in result.provenance.attempts.
For a workflow that requires several exact products, declare the complete set before acquisition and gate dependent processing on the resolved identities:
expected = [request_a.identity, request_b.identity]
available = [
result_a.provenance.resolved_identity,
result_b.provenance.resolved_identity,
]
data.validate_exact_product_set(expected, available)
The gate rejects empty declarations, duplicates, missing products, undeclared
products, and same-filename identities with different prediction metadata. It
returns only by completing successfully; otherwise it raises
ExactProductSetError. A format version resolved from validated bytes may be
present only on the available identity.
For SP3 observed/predicted timing, use sp3.prediction_summary(). It reads the
record flags in the product; issue times and catalog fields are not substitutes
for that metadata.
Exact SP3 content can be validated independently of acquisition:
from pathlib import Path
import sidereon
exact = sidereon.ExactSp3Request.from_identity(request.identity)
sp3, coverage = sidereon.parse_exact_sp3(Path(result.path).read_bytes(), exact)
assert coverage in {
sidereon.ExactSp3Coverage.HALF_OPEN,
sidereon.ExactSp3Coverage.INCLUSIVE,
}
The exact gate requires a finite, positive, supported requested cadence; a
matching finite header cadence; matching line-1 and line-2 start metadata; a
complete SP3 header and terminal EOF; a declared epoch count equal to the
parsed count; the expected producer agency; and a strictly regular parsed epoch
grid. For a one-day five-minute request, both 288 half-open epochs through 23:55
and 289 inclusive epochs through the next midnight are valid. A 287-epoch,
longer, irregular, cadence-mismatched, or metadata-inconsistent product raises
ExactSp3ValidationError. Parsed Sp3 values expose
declared_epoch_count and declared_start_j2000_s for audit use.
Sources and caller input
The supported source descriptors are:
Distribution.direct()for the cataloged analysis-center or IGS URL;Distribution.nasa_cddis()for the official CDDIS HTTPS archive;Distribution.local_file(path, compression="auto");Distribution.in_memory(content, compression="auto").
SP3 CDDIS paths use the GNSS-products GPS-week directory. Current long-name
IONEX paths use ionex/<year>/<day-of-year>. Both retain the exact official
filename. CDDIS packages current long-name products with gzip. IGS combined
final SP3 before GPS week 2238 uses the official igs<week><day>.sp3.Z name and
Unix-compress packaging; Python accepts compression="unix_compress" and
detects the .Z magic bytes in compression="auto" input. Unix-compress
decoding validates terminal code completeness and padding before product
parsing, so a structurally partial archive fails decompression.
Gzip decoding follows the RFC 1952 member-sequence model: each member must have a complete header, DEFLATE stream, CRC32, ISIZE, and end marker. Valid concatenated members are decoded under one cumulative output cap. Arbitrary bytes before, between, or after members, plus an incomplete or corrupt member at any position, fail before product parsing or cache publication.
IGS combined final SP3 begins at GPS week 0730. Before week 2238, Sidereon
derives the verified short filename and CDDIS location; at week 2238 it switches
to IGS0OPSFIN_<epoch>_01D_15M_ORB.SP3.gz. Current direct BKG locations use the
long filename. The reviewed BKG listings did not establish one uniform legacy
direct layout, so a historical direct request is rejected instead of guessed.
IGS broadcast navigation retains its existing BRDC00WRD catalog behavior.
Use data.product_solution_class("igs", "sp3") for "final" and
data.product_solution_class("igs", "nav") for "broadcast"; classification
is intentionally product-aware rather than inferred from the center alone.
data.supported_samples(center, content, date, issue) returns only sampling
tokens evidenced for that exact catalog line. It captures the GFZ rapid date
boundary and the issue-level ESA/GFZ ultra-rapid transitions; constructors
enforce the same set before deriving a filename, URL, identity, or cache key.
Omitting issue for an issue-based query selects its 0000 publication, while
constructing the product still requires the issue explicitly.
data.sp3_content_start_convention(center, date, issue) exposes the core
catalog's relationship between an SP3 filename epoch and the product's first
content epoch. It returns FILENAME_EPOCH for aligned products and
FILENAME_EPOCH_MINUS_ONE_DAY for the historical GFZ ultra-rapid convention;
each enum value also provides content_start_offset_s. The issue is validated
against the selected center's published issue schedule.
CODE product families are routed independently through AIUB's HTTPS service:
SP3 and clock: https://www.aiub.unibe.ch/download/CODE_MGEX/CODE/<year>/...
final IONEX: https://www.aiub.unibe.ch/download/CODE/<year>/...
rapid IONEX: https://www.aiub.unibe.ch/download/CODE/...
predicted P1: https://www.aiub.unibe.ch/download/CODE/IONO/P1/<year>/...
predicted P2: https://www.aiub.unibe.ch/download/CODE/IONO/P2/<year>/...
The current cod catalog line is not applied to dates before GPS week 2238;
those legacy CODE products use different names and directories that are not yet
modeled by this API.
Other long-name SP3 families are likewise bounded by their verified public
archive eras: ESA final starts on 2014-01-05, GFZ rapid on 2020-05-13, ESA
ultra-rapid on 2022-10-04, and GFZ ultra-rapid on 2020-10-06. IGS ultra-rapid
is modeled only from GPS week 2238. ESA ultra-rapid defaults to 15M through
the 2025-02-02 0600 issue and 05M from the 1200 issue; the date-only query
uses the start-of-day (0000) convention. GFZ ultra-rapid defaults to 15M
through 2021-05-15 and 05M from 2021-05-16.
Ultra-rapid acquisition enumerates only dated cadence/span variants evidenced
for the exact center, date, and issue. The GFZ 2021-05-15 0000 issue is the
only cataloged two-candidate overlap. AIUB's separately documented
COD0OPSULT.SP3 is a moving two-day snapshot, not an exact alias for CODE's
dated 01D_05M product, so it is not a fallback candidate. A missing CODE dated
object is reported as absent rather than assigned bytes from that snapshot.
CDDIS distribution is rejected for every pre-week-2238 long-name SP3 or IONEX
identity. The one supported historical CDDIS SP3 exception is the IGS
combined-final family, whose exact identity uses the documented legacy short
name and .Z packaging; it is not a long-name alias.
CDDIS support is exact-product specific, not publisher-wide. Sidereon does not
map ESA's ESA0MGNFIN final SP3 identity to CDDIS in any era: the official ESA
archive serves that line directly, while the public CDDIS evidence reviewed for
this release does not establish an exact ESA0MGNFIN object. Current long-name
IONEX identities use CDDIS's documented
ionex/<year>/<day-of-year>/<official-filename>.gz layout; historical long-name
identities are not projected backward across the week-2238 transition.
NASA documents CDDIS HTTPS access and Earthdata Login at:
- https://www.earthdata.nasa.gov/centers/cddis-daac/archive-access
- https://urs.earthdata.nasa.gov/documentation/for_users/data_access/curl_and_wget
- https://urs.earthdata.nasa.gov/documentation/for_users/data_access/python_user_token_script
The product paths and long filenames follow NASA's GNSS product pages and the IGS long-product-filename guidelines:
- https://www.earthdata.nasa.gov/data/space-geodesy-techniques/gnss/precise-orbits-product
- https://www.earthdata.nasa.gov/data/space-geodesy-techniques/gnss/atmospheric-products
- https://files.igs.org/pub/resource/guidelines/Guidelines_for_Long_Product_Filenames_in_the_IGS.pdf
Earthdata credentials
Credentials are always caller supplied. A bearer token can be passed with
EarthdataAuth.bearer(token). The value is excluded from object repr, errors,
source-attempt records, and provenance. Authorization headers are sent only to
the allowed CDDIS and Earthdata Login hosts.
Earthdata's documented netrc mechanism is also supported:
machine urs.earthdata.nasa.gov login EARTHDATA_USERNAME password EARTHDATA_PASSWORD
result = data.acquire(
request,
earthdata_auth=data.EarthdataAuth.from_netrc(),
)
The HTTP client retains cookies during the deliberate CDDIS-to-URS redirect flow. Redirect targets are restricted to the documented NASA hosts; direct analysis-center redirects retain the existing Sidereon host policy. Query strings, user information, cookies, bearer tokens, and passwords are removed from recorded URLs.
Provenance
AcquisitionProvenance records:
- requested and parsed/resolved identity;
- publisher, distributor, and official filename;
- sanitized original and final public URLs;
- UTC retrieval time, byte counts, SHA-256 hashes, compression, ETag, and Last-Modified when present;
- cache-hit versus network retrieval;
- each failed explicitly allowed source attempted before success.
The decompressed standard product and exact downloaded archive bytes are kept separately. The provenance sidecar contains no credentials.
Merged SP3 provenance
data.fetch_merged_sp3 acquires every successful candidate through this same
exact path. Each report.contributors entry carries an artifact_identity
with the requested and parsed identities, distributor, official filename,
decompressed and archive hashes and lengths, and compression. Its separate
acquisition_facts contains the sanitized URLs, retrieval time, cache status,
HTTP metadata, and prior failed attempts.
The report's stable_input_identity is computed by the Rust core from the
complete artifact identities and effective Sp3MergeOptions. It is unchanged
by cache hits, retrieval timestamps, mapping order, or mean/median contributor
enumeration. Precedence contributor order is input priority and remains
identity-bearing. Changing an artifact, resolved identity, contributor set, or
merge policy changes the identity. input_identity_schema_version identifies
the canonical encoding. Calling data.sp3_merge_input_identity directly returns
the stable ID together with the canonical contributor order and, for precedence
merges, the distinct ordered priority contributors. Two-value unpacking remains
available for callers that only need (schema_version, stable_id).
An incomplete or inconsistent artifact record is rejected rather than hashed.
The JSON-safe merge_policy records every effective option. When precedence
combining is selected, precedence_artifact_sha256 records the ordered artifact
priority; mean and median policies leave that list empty.
merged, report = data.fetch_merged_sp3(day, ["cod", "esa"])
persisted_report_json = json.dumps(report.to_dict(), sort_keys=True)
path, same_report = data.fetch_merged_sp3_file(
day, ["cod", "esa"], "merged.sp3", return_report=True
)
assert data.verify_merge_report(json.loads(persisted_report_json))
Persisted-report verification is exact-schema and non-coercive at every nested
level. It rechecks contributor filename, catalog pattern, center, date, and issue
against the authenticated artifact identity; contributors and absent centers
must exactly partition requested_centers; count and metric domains must be
internally consistent. Unknown authorization, cookie, secret, or local-path
fields are rejected. Moving latest-product snapshots are not assigned an exact
dated identity and are excluded from candidate acquisition and report
verification.
The default file-helper return remains the written path for compatibility.
Neither report.to_dict() nor the stable identity includes credentials,
cookies, authorization headers, cache directories, or temporary paths.
Cache behavior
The cache key includes source plus all exact identity fields. Products from different publishers, solution classes, issues, dates, cadences, or families cannot collide. A cache hit is accepted only after checking the decompressed and archive hashes, lengths, stored identity, provenance, and a fresh SP3 or IONEX parse with coverage-start and cadence checks.
Each accepted entry is one immutable transaction containing the decompressed product, original archive, and provenance. A SHA-256-bound commit record names that transaction. The commit record is replaced atomically only after all three files and their directories have been synchronized. Readers follow only the commit record and then repeat the content-hash, length, identity, source, caller-checksum, and parsed-product checks.
On Linux and macOS, acquisition delegates to the shared Rust transaction
implementation. Concurrent processes and threads use its per-entry advisory
lock across cache validation, acquisition, and commit, so a waiter rechecks and
reuses the completed entry instead of downloading it again.
cache_lock_timeout_s bounds the wait (30 seconds by default); a timeout is a
terminal CacheWriteFailure and does not authorize trying another source. The
operating system releases the lock if its owner exits or is killed. A later lock
owner may remove uncommitted transaction directories without guessing whether
a writer is still alive.
Publication relies on same-filesystem atomic rename/replace, fsync of each
file, and fsync of the entry, entries, and commit-record directories. A process
death or power loss at a publication boundary therefore leaves the previous
complete commit or no acceptable commit. Valid cache triples from the
0.29.0-0.29.2 layout are fully revalidated and migrated without a remote
request. The legacy files are then ignored. result.path retains the official
filename inside the immutable transaction directory.
These crash and cross-process guarantees are for local filesystems on Linux and macOS. Filesystems that do not honor POSIX advisory locks, atomic same-directory rename, or directory synchronization are outside the guarantee. Existing verified entries are returned without a remote request, including during a remote outage.
Low-level consumers can use sidereon.exact_cache.ExactProductCache,
entry_lock, and read directly. Those calls authenticate bytes and identity;
the caller remains responsible for transport and product-format validation.
Typed failures
Exact acquisition distinguishes authentication required, authentication failed, authorization denied, not published (404), retired endpoint (410), redirect policy, malformed URL, timeout/connection/HTTP transport, HTML or invalid content, content-length mismatch, gzip/Unix-compress failure, caller checksum, product parse/semantic validation, and cache read/write failures.
An ordinary 404/not-published response or retired endpoint may advance to the next explicitly allowed distributor for the same exact identity. An exhausted timeout, connection, 408, 429, or server failure may try another distributor, but is preserved if no source succeeds. Malformed data, parsing, checksum, cadence, span, identity, authentication/authorization, redirect-policy, cache, and caller-configuration failures are terminal and do not authorize fallback. No official source reviewed here documented a moving-alias race that justified a validation-error exception, so none is implemented.
Retries are bounded and limited to connection/timeouts, 408, 429, and server errors. NASA publishes no universal numeric CDDIS request limit; callers should keep concurrency modest, honor service responses, and use a verified local cache. NASA's public data-use guidance is at https://www.earthdata.nasa.gov/engage/open-data-services-software/data-use-policy.
Evidence for the 0.33 catalog and validation changes
All sources in this table were accessed on 2026-07-20.
| Change | Official public evidence | Accessed |
|---|---|---|
| A gzip file is a sequence of members; each member carries its own CRC32 and ISIZE trailer. Python acquisition accepts only a complete, valid sequence under the caller's cumulative output limit. | RFC 1952 | 2026-07-21 |
| IGS combined final orbits begin at GPS week 0730. | 1994 IGS Annual Report | 2026-07-20 |
IGS final SP3 changes from the short .sp3.Z convention to the long .SP3.gz convention at GPS week 2238. | IGS transition guideline, IGSMAIL-8256, week 2237 CDDIS object, week 2238 CDDIS object | 2026-07-20 |
CDDIS's documented legacy orbit convention and the IGS week-2238 transition support one modeled pre-transition mapping: the IGS combined-final short name with .Z. Other centers' pre-transition long names are not projected into CDDIS. | NASA precise-orbit convention, IGS transition guideline, week 2237 legacy object, week 2238 long-name object | 2026-07-20 |
ESA publishes the ESA0MGNFIN final-SP3 line from its official archive; the reviewed public CDDIS convention does not establish an exact CDDIS mapping for that identity, so Sidereon rejects the distributor instead of substituting another ESA line. | ESA week-2320 listing, ESA final-SP3 object, NASA precise-orbit convention | 2026-07-20 |
BKG's current path is IGS/products/<week>, while transition-era listings do not establish one universal historical direct path. | BKG week 2238, BKG legacy week 2235, BKG week 2236 | 2026-07-20 |
SP3 declares start/count on line 1 and start/cadence metadata on line 2; its mandatory records and terminal EOF are integrity inputs. | SP3-d specification | 2026-07-20 |
AIUB's current CODE families use product-specific CODE_MGEX/CODE/<year>, CODE/<year>, CODE, and predicted IONEX tier directories. | AIUB product documentation, MGEX 2026 listing, CODE 2026 listing, P1 listing, P2 listing | 2026-07-20 |
GFZ rapid SP3 used 15M through 2021 day 137 and 05M from day 138. | GFZ week 2158 listing | 2026-07-20 |
| ESA's MGEX final-SP3 and clock archive begins on 2014-01-05. | preceding week 1773, first week 1774 listing, first SP3 object, first clock object | 2026-07-20 |
| GFZ's rapid-SP3 and clock listing begins on 2020-05-13. | GFZ week-2105 listing, first rapid SP3 object, first rapid clock object | 2026-07-20 |
| IGS operational ultra-rapid long names start with GPS week 2238. | IGS transition guideline, BKG week-2238 listing, first long-name ultra SP3 object | 2026-07-20 |
| ESA's operational ultra-rapid SP3 line begins on 2022-10-04. | preceding week 2229, week-2230 listing, first ultra SP3 object | 2026-07-20 |
| GFZ's operational ultra-rapid SP3 listing begins on 2020-10-06. | GFZ week-2126 listing, first ultra SP3 object | 2026-07-20 |
ESA ultra-rapid SP3 changes from 15M at the 2025-02-02 0600 issue to 05M at 1200. | ESA week-2352 listing, 0600 15M object, 1200 05M object | 2026-07-20 |
GFZ ultra-rapid SP3 defaults to 15M through 2021-05-15 and 05M from 2021-05-16; one 0000 05M object overlaps the otherwise-15M final day. | GFZ week-2157 listing, last 15M issue, overlapping 05M object, GFZ week-2158 listing, first next-day 05M object | 2026-07-20 |
| CDDIS IONEX filenames transitioned from historical short names toward long names beginning at week 2238, with center-specific timing. Sidereon therefore does not derive a pre-transition CDDIS URL for a caller's long-name IONEX identity. | IGS ionospheric products, NASA Earthdata support clarification | 2026-07-20 |
| CDDIS HTTPS access uses Earthdata Login and keeps product archive paths distinct from product identity. | NASA CDDIS archive access, NASA precise-orbit product | 2026-07-20 |
Compatibility assessment: the Python additions are source-compatible. Catalog
derivation is deliberately stricter for unsupported historical CODE dates and
dates before the verified SP3-family publication floors, known center/product
mismatches now fail before HTTP, and integrity failures no longer fall through
to another distributor. Those observable corrections plus the additive
exact-SP3 API put these changes in the 0.33 minor line. Python 0.33.1
aligns with the core source-package compliance patch on that line.
Adding another distributor
A new distributor supplies transport URL and compression metadata for an
existing ProductIdentity. It must not rewrite the identity. Its transport
must apply the same redirect, size, content, decompression, parse, provenance,
and atomic-cache requirements before returning bytes.
The legacy data.fetch(product) API is unchanged and continues to use the
cataloged direct archive and legacy cache location. Callers migrate only when
they want explicit distributor selection and returned provenance.