Added: Why section, env vars table, merge strategy explanation, NORAD encoding table, regime classification, library usage examples, SQL output format, and development instructions.
6.3 KiB
pg-orrery-catalog
TLE catalog builder for pg_orrery — download, merge, and load satellite catalogs from Space-Track, CelesTrak, and SatNOGS into PostgreSQL.
Why
pg_orrery needs TLEs in a table. The satellite catalog has 30,000+ tracked objects spread across multiple data sources, each with different freshness. Getting them all merged and loaded into PostgreSQL is tedious: download from each source, handle different auth methods, deduplicate by epoch, format as SQL, pipe to psql.
pg-orrery-catalog handles the whole pipeline.
Install
# Run directly (no install needed)
uvx pg-orrery-catalog --help
# Or install
uv pip install pg-orrery-catalog
# For direct database loading (adds psycopg)
uv pip install "pg-orrery-catalog[pg]"
Requires Python 3.11+.
Usage
Download TLE data
pg-orrery-catalog download # All configured sources
pg-orrery-catalog download --source celestrak # CelesTrak only (no auth)
pg-orrery-catalog download --source spacetrack # Space-Track (needs credentials)
pg-orrery-catalog download --force # Re-download even if cached
Files are cached in ~/.cache/pg-orrery-catalog/ and reused for 24 hours.
Build a merged catalog
pg-orrery-catalog build | psql -d mydb # SQL to stdout
pg-orrery-catalog build --table satellites -o catalog.sql # Save SQL file
pg-orrery-catalog build --format 3le -o merged.tle # 3LE text output
pg-orrery-catalog build --format json -o catalog.json # JSON with metadata
When the same NORAD ID appears in multiple sources, the entry with the newest epoch wins. This means CelesTrak SupGP data (operator-provided, fresher epochs) automatically overrides stale Space-Track entries.
Build from specific files
pg-orrery-catalog build /path/to/spacetrack.tle /path/to/celestrak.tle
Load directly into PostgreSQL
pg-orrery-catalog load \
--database-url postgresql:///mydb \
--table satellites \
--create-index
Check status
pg-orrery-catalog info --cache
Configuration
Three layers (highest precedence first): CLI flags > environment variables > config file.
Environment variables
| Variable | Purpose |
|---|---|
SPACETRACK_USER |
Space-Track login email |
SPACETRACK_PASSWORD |
Space-Track password |
SOCKS_PROXY |
SOCKS5 proxy for CelesTrak/SatNOGS (e.g. localhost:1080) |
DATABASE_URL |
PostgreSQL connection URL for load command |
PG_ORRERY_TABLE |
Default table name |
Config file
~/.config/pg-orrery-catalog/config.toml:
[spacetrack]
user = "you@example.com"
password = "secret"
[celestrak]
proxy = "localhost:1080"
supgp_groups = ["starlink", "oneweb", "planet", "orbcomm"]
[output]
table = "satellites"
[database]
url = "postgresql://localhost/mydb"
Sources
| Source | Auth | Coverage | Freshness |
|---|---|---|---|
| Space-Track | Login required | Full catalog (~30k+ on-orbit) | Hours to days |
| CelesTrak | None | Active sats + operator SupGP | Minutes to hours |
| SatNOGS | None | Community-tracked objects | Varies |
CelesTrak's supplemental GP groups are configured via supgp_groups. These contain operator-submitted ephemerides (SpaceX for Starlink, OneWeb, Planet, Orbcomm) that are typically hours fresher than Space-Track's catalog.
Merge strategy
Sources are processed in order: Space-Track (base catalog), CelesTrak active, SatNOGS, then SupGP groups (freshest last). For each NORAD ID, the entry with the newest TLE epoch wins.
Example merge output:
spacetrack_everything: 33053 objects (33053 new, 0 updated)
celestrak_active: 14376 objects (2 new, 0 updated)
satnogs_full: 1488 objects (121 new, 5 updated)
supgp_starlink: 9703 objects (77 new, 7398 updated)
Total: 33253 unique objects
Regimes: LEO: 31542, GEO: 1203, MEO: 385, HEO: 123
The 7,398 Starlink updates are fresher epochs from SpaceX overriding stale Space-Track entries.
NORAD ID decoding
TLEs use a 5-character NORAD catalog field. With 100,000+ tracked objects, numeric encoding ran out of space. pg-orrery-catalog handles all four encoding cases, matching the C implementation in pg_orrery's vendored SGP4 library:
| Case | Format | Range | Example |
|---|---|---|---|
| Traditional | ddddd |
0 -- 99,999 | 25544 = 25,544 (ISS) |
| Alpha-5 | Ldddd |
100,000 -- 339,999 | T0002 = 270,002 |
| Super-5 (3) | xxxxX |
340,000 -- 906,309,663 | 0000A = 340,000 |
| Super-5 (4) | xxxXd |
906,309,664+ | 000A0 = 906,309,664 |
Alpha-5 skips letters I and O (ambiguous with 1 and 0). Super-5 uses a base-64 alphabet: 0-9, A-Z, a-z, +, -.
SQL output format
The generated SQL matches pg_orrery conventions:
DROP TABLE IF EXISTS satellites;
CREATE TABLE IF NOT EXISTS satellites (
id serial,
name text,
tle tle
);
INSERT INTO satellites (name, tle) VALUES ('ISS (ZARYA)', E'1 25544U 98067A ...\n2 25544 51.6400 ...');
Orbital regime classification
Objects are classified by mean motion (revolutions per day):
| Regime | Mean Motion | Description |
|---|---|---|
| LEO | > 11.25 rev/day | Low Earth Orbit (period < 128 min) |
| MEO | > 1.8 rev/day | Medium Earth Orbit (GPS, navigation) |
| GEO | > 0.9 rev/day | Near-synchronous (comms, weather) |
| HEO | <= 0.9 rev/day | Highly elliptical (Molniya, tundra, GTO) |
Library usage
from pg_orrery_catalog.tle import decode_norad, parse_3le_file
from pg_orrery_catalog.catalog import merge_sources
from pg_orrery_catalog.regime import regime_summary
from pg_orrery_catalog.output.sql import generate_sql
# Parse and merge multiple files
merged, stats = merge_sources(["spacetrack.tle", "celestrak.tle"])
print(f"{stats.total_unique} unique objects")
# Regime breakdown
print(regime_summary(merged))
# {'LEO': 31542, 'MEO': 385, 'GEO': 1203, 'HEO': 123}
# Generate pg_orrery-compatible SQL
sql = generate_sql(merged, table="my_catalog")
Development
git clone git@git.supported.systems:warehack.ing/pg-orrery-catalog.git
cd pg-orrery-catalog
uv venv && uv pip install -e ".[dev]"
pytest tests/ -v # 58 tests
ruff check src/ tests/ # Lint
License
MIT