Implement pg-orrery-catalog: TLE catalog builder for pg_orrery
Core modules: - tle.py: NORAD decoding (Alpha-5 + Super-5, matching get_el.c), 3LE/2LE parsing, TLERecord dataclass with epoch-based dedup - config.py: TOML config + env var overlay (XDG-compliant paths) - cache.py: File-based cache with staleness checking - catalog.py: Multi-source merge with MergeStats tracking - regime.py: LEO/MEO/GEO/HEO classification by mean motion Source downloaders (httpx): - celestrak.py: Active catalog + supplemental GP groups - satnogs.py: JSON API with 3LE conversion - spacetrack.py: POST auth flow, bulk GP download Output formatters: - sql.py: pg_orrery-compatible INSERT generation (E'' strings) - tle_file.py: Standard 3LE text output - json_out.py: JSON with orbital metadata and regime CLI (Click + Rich): - download: Cache TLEs from all sources - build: Merge + output SQL/3LE/JSON (pipes to psql) - load: Direct DB load via psycopg (optional [pg] extra) - info: Cache stats and configuration display 58 tests covering NORAD decoding (all 4 encoding cases), parsing, merge/dedup, SQL escaping, regime classification.
This commit is contained in:
parent
23eb205fe8
commit
1d36729bed
18 changed files with 2079 additions and 0 deletions
35
src/pg_orrery_catalog/regime.py
Normal file
35
src/pg_orrery_catalog/regime.py
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
"""Orbital regime classification based on mean motion.
|
||||
|
||||
Thresholds match the bench/load_bench.sh SQL query:
|
||||
LEO: mean_motion > 11.25 rev/day (period < 128 min, alt < ~2000 km)
|
||||
MEO: mean_motion > 1.8 rev/day (period < 800 min)
|
||||
GEO: mean_motion > 0.9 rev/day (near-synchronous)
|
||||
HEO: everything else (Molniya, tundra, GTO, etc.)
|
||||
"""
|
||||
|
||||
from .tle import TLERecord
|
||||
|
||||
|
||||
def classify_regime(mean_motion: float) -> str:
|
||||
"""Classify orbital regime from mean motion (revs/day)."""
|
||||
if mean_motion > 11.25:
|
||||
return "LEO"
|
||||
if mean_motion > 1.8:
|
||||
return "MEO"
|
||||
if mean_motion > 0.9:
|
||||
return "GEO"
|
||||
return "HEO"
|
||||
|
||||
|
||||
def classify_record(rec: TLERecord) -> str:
|
||||
"""Classify a TLERecord's orbital regime."""
|
||||
return classify_regime(rec.mean_motion)
|
||||
|
||||
|
||||
def regime_summary(records: dict[int, TLERecord]) -> dict[str, int]:
|
||||
"""Count objects per regime."""
|
||||
counts: dict[str, int] = {"LEO": 0, "MEO": 0, "GEO": 0, "HEO": 0}
|
||||
for rec in records.values():
|
||||
regime = classify_record(rec)
|
||||
counts[regime] = counts.get(regime, 0) + 1
|
||||
return counts
|
||||
Loading…
Add table
Add a link
Reference in a new issue