# pg_orbit — PostgreSQL Extension for Orbital Mechanics ## What This Is A PostgreSQL extension that makes TLE/orbital data first-class types — the way PostGIS does for geographic data. Native C extension using PGXS, wrapping Bill Gray's `sat_code` SGP4/SDP4 implementation. ## Build System ```bash make # Compile with PGXS make install # Install to PostgreSQL extensions dir make installcheck # Run regression tests ``` Requires: PostgreSQL 14+ development headers (`pg_config` in PATH), GCC, Make. ## Project Layout ``` pg_orbit.control # Extension metadata Makefile # PGXS build sql/ pg_orbit--0.1.0.sql # All type/function/operator definitions src/ pg_orbit.c # PG_MODULE_MAGIC entry point tle_type.c # TLE custom type (input/output/binary/accessors) eci_type.c # ECI position type + geodetic/topocentric types observer_type.c # Observer location type with flexible parsing sgp4_funcs.c # sgp4_propagate(), sgp4_propagate_series() coord_funcs.c # eci_to_geodetic(), eci_to_topocentric(), subsatellite_point() pass_funcs.c # next_pass(), predict_passes(), pass_visible() gist_tle.c # GiST operator class for altitude-band indexing types.h # Shared struct definitions lib/ sat_code/ # Bill Gray's SGP4 (MIT license, git submodule) test/ sql/ # Regression test SQL expected/ # Expected output data/ vallado_518.csv # 518 verification test vectors docs/ DESIGN.md # Architecture decisions, theory-to-code mappings ``` ## Type System ### Core Types (all varlena or fixed-size, stored in tuples) | Type | Storage | Description | |------|---------|-------------| | `tle` | ~160 bytes fixed | Parsed mean elements (not raw text) | | `eci_position` | 48 bytes | x,y,z + vx,vy,vz (km, km/s) in TEME | | `geodetic` | 24 bytes | lat, lon (radians), alt (km) above WGS-84 | | `topocentric` | 32 bytes | azimuth, elevation, range, range_rate | | `observer` | 24 bytes | lat, lon (radians), alt_m (meters) | | `pass_event` | 56 bytes | AOS/MAX/LOS times + max_el + AOS/LOS az | ### TLE Internal Struct Stores all parsed mean elements from the two-line format: - epoch (Julian date, float64) - inclination, eccentricity, RAAN, arg_perigee, mean_anomaly (radians, float64) - mean_motion (rev/day, float64), mean_motion_dot, mean_motion_ddot - bstar (drag coefficient, float64) - norad_id (int32), elset_num (int32), rev_num (int32) - classification (char), intl_designator (8 chars) - ephemeris_type (int8) ## Constant Chain of Custody **This is the most critical design constraint.** TLEs are mean elements fitted using WGS-72 constants. The elements absorb geodetic model biases — using WGS-84 constants for propagation silently corrupts position accuracy by kilometers. ### Rules 1. **SGP4 propagation**: WGS-72 constants ONLY (mu, ae, J2, J3, J4, ke) 2. **Coordinate output** (geodetic, topocentric): Convert to WGS-84 (a=6378.137km, f=1/298.257223563) 3. **TEME frame**: Use only 4 of 106 IAU-80 nutation terms (matching SGP4's internal model) 4. **Never mix**: WGS-72 propagation + WGS-84 output. No other combination. ### WGS-72 Constants (from Hoots & Roehrich STR#3) ```c #define WGS72_MU 398600.8 /* km^3/s^2 */ #define WGS72_AE 6378.135 /* km */ #define WGS72_J2 0.001082616 #define WGS72_J3 -0.00000253881 #define WGS72_J4 -0.00000165597 #define WGS72_KE 0.0743669161 /* (min)^(-1), = sqrt(mu) * 60 / ae^(3/2) */ #define WGS72_XPDOTP 1440.0 / (2.0 * M_PI) /* min/rev */ ``` ### WGS-84 Constants (for output only) ```c #define WGS84_A 6378.137 /* km */ #define WGS84_F (1.0 / 298.257223563) #define WGS84_E2 (WGS84_F * (2.0 - WGS84_F)) ``` ## sat_code Submodule Bill Gray's SGP4 implementation: https://github.com/Bill-Gray/sat_code Key files we use: - `sgp4.c` / `sgp4.h` — SGP4/SDP4 propagator - `norad.h` — TLE struct definitions and constants The submodule lives at `lib/sat_code/`. To initialize: ```bash git submodule update --init ``` ### Integration Pattern ```c #include "lib/sat_code/norad.h" // Parse TLE lines into sat_code's tle_t struct // Call SGP4_init() once per TLE // Call SGP4() with minutes-since-epoch for each propagation ``` ## Testing ### Vallado 518 Test Vectors The definitive SGP4 verification dataset. Each row: NORAD ID, minutes since epoch, expected x,y,z,vx,vy,vz. All 518 must pass to machine epsilon before any other work proceeds. ### Regression Tests Standard PostgreSQL `make installcheck` framework: - `test/sql/*.sql` — test queries - `test/expected/*.out` — expected output - Tests run against a temporary database ### Test Categories 1. **tle_parse** — TLE input/output round-trip, malformed input rejection 2. **sgp4_propagate** — Vallado vectors, edge cases (deep space, high eccentricity) 3. **coord_transforms** — TEME->geodetic, TEME->topocentric accuracy 4. **pass_prediction** — Known ISS passes, edge cases (polar, retrograde) 5. **gist_index** — Index scan vs sequential scan equivalence ## Coding Style - Standard PostgreSQL extension C style - `ereport(ERROR, ...)` for user-facing errors, never `elog(ERROR, ...)` - All memory allocation through `palloc`/`pfree` (PostgreSQL memory contexts) - Comments explain "why", not "what" - No global mutable state — all computation from function arguments - Functions that call `SGP4()` must handle the error return code ## Git Conventions - One commit per logical change - Branch per phase: `phase/1-tle-sgp4`, `phase/2-coordinates`, etc. - Tag releases: `v0.1.0`, `v0.2.0`, etc. - Commit messages: imperative mood, no AI attribution