6 custom types (tle, eci_position, geodetic, topocentric, observer, pass_event), 67 SQL functions, 2 operators (&&, <->), and a GiST operator class for altitude-band indexing. Wraps Bill Gray's sat_code for SGP4/SDP4 propagation with WGS-72 constants for propagation and WGS-84 for coordinate output. All 5 regression tests pass on PG 18.
148 lines
5.7 KiB
Markdown
148 lines
5.7 KiB
Markdown
# 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
|