README v0.2.0 rewrite and docs deployment infrastructure
README covers all 57 functions across 9 domains with quick-start examples, capability matrix, body ID tables, and performance benchmarks. Links to Starlight docs site for detailed reference. Adds Docker Compose deployment for docs site behind caddy-docker-proxy with dev/prod modes and Vite HMR support through reverse proxy.
This commit is contained in:
parent
76767c2909
commit
a78e8eb27c
425
README.md
425
README.md
@ -1,305 +1,230 @@
|
||||
# pg_orbit
|
||||
|
||||
Orbital mechanics types and functions for PostgreSQL.
|
||||
Solar system computation for PostgreSQL.
|
||||
|
||||
pg_orbit adds native SQL types for TLEs, orbital positions, ground stations, and
|
||||
satellite passes. It wraps Bill Gray's [sat_code](https://github.com/Bill-Gray/sat_code)
|
||||
(MIT) for SGP4/SDP4 propagation, provides coordinate transforms between inertial
|
||||
and ground-referenced frames, predicts passes over observer locations, and supports
|
||||
GiST-indexed conjunction screening on altitude bands.
|
||||
pg_orbit moves orbital mechanics inside your database. Track satellites, compute
|
||||
planet positions, observe 19 planetary moons, predict Jupiter radio bursts, and
|
||||
plan interplanetary trajectories — all from standard SQL. Think PostGIS, but for
|
||||
objects in space.
|
||||
|
||||
Think PostGIS, but for objects in orbit.
|
||||
57 functions. 7 custom types. All `PARALLEL SAFE`. Zero external dependencies at
|
||||
runtime.
|
||||
|
||||
## Installation
|
||||
|
||||
Requirements:
|
||||
- PostgreSQL 14+ development headers (`pg_config` in PATH)
|
||||
- GCC and Make
|
||||
- C++ compiler (for sat_code)
|
||||
### Docker (recommended)
|
||||
|
||||
```bash
|
||||
git clone --recurse-submodules https://github.com/...
|
||||
cd pg_orbit
|
||||
make
|
||||
sudo make install
|
||||
docker run -d --name pg_orbit \
|
||||
-e POSTGRES_PASSWORD=orbit \
|
||||
-p 5499:5432 \
|
||||
git.supported.systems/warehack.ing/pg_orbit:pg17
|
||||
```
|
||||
|
||||
Then in your database:
|
||||
```bash
|
||||
psql -h localhost -p 5499 -U postgres -c "CREATE EXTENSION pg_orbit;"
|
||||
```
|
||||
|
||||
### Build from Source
|
||||
|
||||
Requires PostgreSQL 17 development headers and a C/C++ toolchain.
|
||||
|
||||
```bash
|
||||
git clone https://git.supported.systems/warehack.ing/pg_orbit.git
|
||||
cd pg_orbit
|
||||
git submodule update --init
|
||||
make PG_CONFIG=/usr/bin/pg_config
|
||||
sudo make install PG_CONFIG=/usr/bin/pg_config
|
||||
```
|
||||
|
||||
```sql
|
||||
CREATE EXTENSION pg_orbit;
|
||||
```
|
||||
|
||||
If you cloned without `--recurse-submodules`, initialize the sat_code dependency:
|
||||
|
||||
```bash
|
||||
git submodule update --init
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
**Where is Jupiter right now?**
|
||||
|
||||
```sql
|
||||
-- Create a table with a TLE column
|
||||
CREATE TABLE satellites (
|
||||
norad_id int PRIMARY KEY,
|
||||
name text NOT NULL,
|
||||
tle tle NOT NULL
|
||||
);
|
||||
|
||||
-- Insert a TLE (standard two-line format, concatenated with newline)
|
||||
INSERT INTO satellites VALUES (25544, 'ISS',
|
||||
'1 25544U 98067A 24001.50000000 .00016717 00000-0 10270-3 0 9002
|
||||
2 25544 51.6400 208.5000 0006000 30.0000 330.0000 15.50000000400000');
|
||||
|
||||
-- Propagate to a point in time
|
||||
SELECT sgp4_propagate(tle, now()) FROM satellites WHERE norad_id = 25544;
|
||||
|
||||
-- Subsatellite point (nadir)
|
||||
SELECT subsatellite_point(tle, now()) FROM satellites WHERE norad_id = 25544;
|
||||
|
||||
-- All passes over Boulder, CO in the next 24 hours (min 10 deg elevation)
|
||||
SELECT sat.name, p.*
|
||||
FROM satellites sat,
|
||||
LATERAL predict_passes(sat.tle, '40.0N 105.3W 1655m'::observer,
|
||||
now(), now() + '24h', 10.0) p
|
||||
ORDER BY pass_aos_time(p);
|
||||
|
||||
-- Conjunction screening with GiST index
|
||||
CREATE INDEX ON satellites USING gist (tle);
|
||||
|
||||
SELECT a.name, b.name, tle_distance(a.tle, b.tle, now())
|
||||
FROM satellites a, satellites b
|
||||
WHERE a.tle && b.tle AND a.norad_id < b.norad_id
|
||||
AND tle_distance(a.tle, b.tle, now()) < 10.0;
|
||||
SELECT topo_azimuth(t) AS az,
|
||||
topo_elevation(t) AS el,
|
||||
topo_range(t) / 149597870.7 AS distance_au
|
||||
FROM planet_observe(5, '40.0N 105.3W 1655m'::observer, now()) t;
|
||||
```
|
||||
|
||||
**What's the entire solar system doing?**
|
||||
|
||||
```sql
|
||||
SELECT body_id,
|
||||
CASE body_id
|
||||
WHEN 1 THEN 'Mercury' WHEN 2 THEN 'Venus'
|
||||
WHEN 3 THEN 'Earth' WHEN 4 THEN 'Mars'
|
||||
WHEN 5 THEN 'Jupiter' WHEN 6 THEN 'Saturn'
|
||||
WHEN 7 THEN 'Uranus' WHEN 8 THEN 'Neptune'
|
||||
END AS name,
|
||||
round(helio_distance(planet_heliocentric(body_id, now()))::numeric, 4) AS distance_au
|
||||
FROM generate_series(1, 8) AS body_id;
|
||||
```
|
||||
|
||||
**Predict ISS passes over your location:**
|
||||
|
||||
```sql
|
||||
WITH iss AS (
|
||||
SELECT '1 25544U 98067A 24001.50000000 .00016717 00000-0 10270-3 0 9025
|
||||
2 25544 51.6400 208.9163 0006703 30.1694 61.7520 15.50100486 00001'::tle AS tle
|
||||
)
|
||||
SELECT pass_aos(p) AS rise_time,
|
||||
pass_max_el(p) AS max_elevation,
|
||||
pass_los(p) AS set_time
|
||||
FROM iss, predict_passes(tle, '40.0N 105.3W 1655m'::observer,
|
||||
now(), now() + interval '24 hours', 10.0) p;
|
||||
```
|
||||
|
||||
**When will Jupiter produce radio bursts tonight?**
|
||||
|
||||
```sql
|
||||
SELECT t,
|
||||
round(jupiter_burst_probability(
|
||||
io_phase_angle(t),
|
||||
jupiter_cml('40.0N 105.3W 1655m'::observer, t)
|
||||
)::numeric, 3) AS burst_prob
|
||||
FROM generate_series(now(), now() + interval '12 hours', interval '10 minutes') AS t
|
||||
WHERE jupiter_burst_probability(
|
||||
io_phase_angle(t),
|
||||
jupiter_cml('40.0N 105.3W 1655m'::observer, t)
|
||||
) > 0.3;
|
||||
```
|
||||
|
||||
**Plan an Earth-Mars transfer:**
|
||||
|
||||
```sql
|
||||
SELECT round(c3_departure::numeric, 2) AS c3_depart_km2s2,
|
||||
round(tof_days::numeric, 1) AS flight_days,
|
||||
round(transfer_sma::numeric, 4) AS sma_au
|
||||
FROM lambert_transfer(3, 4, '2028-10-01'::timestamptz, '2029-06-15'::timestamptz);
|
||||
```
|
||||
|
||||
## What It Covers
|
||||
|
||||
| Domain | Theory | Key Functions | Accuracy |
|
||||
|---|---|---|---|
|
||||
| Satellites | SGP4/SDP4 (Brouwer 1959) | `observe()`, `predict_passes()` | ~1 km (LEO, fresh TLE) |
|
||||
| Planets | VSOP87 (Bretagnon 1988) | `planet_observe()`, `planet_heliocentric()` | ~1 arcsecond |
|
||||
| Sun | VSOP87 (Earth vector, inverted) | `sun_observe()` | ~1 arcsecond |
|
||||
| Moon | ELP2000-82B (Chapront 1988) | `moon_observe()` | ~10 arcseconds |
|
||||
| Planetary moons | L1.2, TASS17, GUST86, MarsSat | `galilean_observe()`, etc. | ~1-10 arcseconds |
|
||||
| Stars | J2000 catalog + precession | `star_observe()` | Limited by catalog |
|
||||
| Comets/asteroids | Two-body Keplerian | `kepler_propagate()`, `comet_observe()` | Varies with eccentricity |
|
||||
| Jupiter radio | Carr et al. (1983) sources | `jupiter_burst_probability()` | Empirical probability |
|
||||
| Transfers | Lambert (Izzo 2015) | `lambert_transfer()`, `lambert_c3()` | Ballistic two-body |
|
||||
|
||||
## Types
|
||||
|
||||
| Type | Size | Description |
|
||||
|------|------|-------------|
|
||||
| `tle` | 112 bytes | Parsed mean orbital elements (epoch, Keplerian elements, drag terms, identifiers). Stored as a fixed-size struct, not raw text. |
|
||||
| `eci_position` | 48 bytes | Position (km) and velocity (km/s) in the True Equator Mean Equinox (TEME) frame. |
|
||||
| `geodetic` | 24 bytes | Latitude/longitude (degrees) and altitude (km) on the WGS-84 ellipsoid. |
|
||||
| `topocentric` | 32 bytes | Azimuth, elevation (degrees), slant range (km), and range rate (km/s) relative to an observer. |
|
||||
| `observer` | 24 bytes | Ground station location. Accepts human-readable input: `'40.0N 105.3W 1655m'` or decimal degrees. |
|
||||
| `pass_event` | 48 bytes | Satellite pass with AOS/MAX/LOS times, max elevation, and AOS/LOS azimuths. |
|
||||
| Type | Bytes | Description |
|
||||
|------|-------|-------------|
|
||||
| `tle` | 112 | Parsed mean orbital elements for SGP4/SDP4 propagation |
|
||||
| `eci_position` | 48 | Position and velocity in TEME frame (km, km/s) |
|
||||
| `geodetic` | 24 | Latitude, longitude, altitude on WGS-84 ellipsoid |
|
||||
| `topocentric` | 32 | Azimuth, elevation, range, range rate relative to observer |
|
||||
| `observer` | 24 | Ground location. Input: `'40.0N 105.3W 1655m'` or decimal degrees |
|
||||
| `pass_event` | 48 | Satellite pass with AOS/TCA/LOS times and azimuths |
|
||||
| `heliocentric` | 24 | Position in AU, ecliptic J2000 frame |
|
||||
|
||||
### Input Formats
|
||||
All types are fixed-size with `STORAGE = plain`. No TOAST overhead.
|
||||
|
||||
**tle** -- Standard two-line format (lines joined by newline):
|
||||
## Body IDs
|
||||
|
||||
```sql
|
||||
SELECT '1 25544U 98067A 24001.50000000 .00016717 00000-0 10270-3 0 9002
|
||||
2 25544 51.6400 208.5000 0006000 30.0000 330.0000 15.50000000400000'::tle;
|
||||
```
|
||||
Planets follow the VSOP87 convention. Planetary moons use per-family indexing.
|
||||
|
||||
**observer** -- Flexible ground station input:
|
||||
|
||||
```sql
|
||||
-- Compass notation with altitude
|
||||
SELECT '40.0N 105.3W 1655m'::observer;
|
||||
|
||||
-- Decimal degrees (positive East, altitude in meters)
|
||||
SELECT '40.0 -105.3 1655'::observer;
|
||||
```
|
||||
|
||||
## Functions
|
||||
|
||||
### TLE Accessors
|
||||
|
||||
| Function | Returns | Description |
|
||||
|----------|---------|-------------|
|
||||
| `tle_norad_id(tle)` | `int4` | NORAD catalog number |
|
||||
| `tle_epoch(tle)` | `float8` | Epoch as Julian date (UTC) |
|
||||
| `tle_inclination(tle)` | `float8` | Inclination in degrees |
|
||||
| `tle_eccentricity(tle)` | `float8` | Eccentricity (dimensionless) |
|
||||
| `tle_raan(tle)` | `float8` | Right ascension of ascending node (degrees) |
|
||||
| `tle_arg_perigee(tle)` | `float8` | Argument of perigee (degrees) |
|
||||
| `tle_mean_anomaly(tle)` | `float8` | Mean anomaly (degrees) |
|
||||
| `tle_mean_motion(tle)` | `float8` | Mean motion (rev/day) |
|
||||
| `tle_bstar(tle)` | `float8` | B* drag coefficient (1/earth-radii) |
|
||||
| `tle_period(tle)` | `float8` | Orbital period (minutes) |
|
||||
| `tle_perigee(tle)` | `float8` | Perigee altitude (km above WGS-72 ellipsoid) |
|
||||
| `tle_apogee(tle)` | `float8` | Apogee altitude (km above WGS-72 ellipsoid) |
|
||||
| `tle_age(tle, timestamptz)` | `float8` | TLE age in days (positive = past epoch) |
|
||||
| `tle_intl_desig(tle)` | `text` | International designator (COSPAR ID) |
|
||||
|
||||
### Propagation
|
||||
|
||||
| Function | Returns | Description |
|
||||
|----------|---------|-------------|
|
||||
| `sgp4_propagate(tle, timestamptz)` | `eci_position` | Propagate to a point in time. Uses SGP4 for near-earth, SDP4 for deep-space. |
|
||||
| `sgp4_propagate_series(tle, start, stop, step)` | `SETOF (t, x, y, z, vx, vy, vz)` | Time series of TEME positions at regular intervals. |
|
||||
| `tle_distance(tle, tle, timestamptz)` | `float8` | Euclidean distance (km) between two objects at a reference time. |
|
||||
|
||||
### Coordinate Transforms
|
||||
|
||||
| Function | Returns | Description |
|
||||
|----------|---------|-------------|
|
||||
| `eci_to_geodetic(eci_position, timestamptz)` | `geodetic` | TEME to WGS-84 geodetic (lat/lon/alt). Requires time for Earth rotation. |
|
||||
| `eci_to_topocentric(eci_position, observer, timestamptz)` | `topocentric` | TEME to observer-relative az/el/range. |
|
||||
| `subsatellite_point(tle, timestamptz)` | `geodetic` | Nadir point on WGS-84 ellipsoid. Propagates internally. |
|
||||
| `ground_track(tle, start, stop, step)` | `SETOF (t, lat, lon, alt)` | Ground track as time series of subsatellite points. |
|
||||
|
||||
### ECI Accessors
|
||||
|
||||
| Function | Returns | Description |
|
||||
|----------|---------|-------------|
|
||||
| `eci_x(eci_position)` | `float8` | X position (km, TEME) |
|
||||
| `eci_y(eci_position)` | `float8` | Y position (km, TEME) |
|
||||
| `eci_z(eci_position)` | `float8` | Z position (km, TEME) |
|
||||
| `eci_vx(eci_position)` | `float8` | X velocity (km/s) |
|
||||
| `eci_vy(eci_position)` | `float8` | Y velocity (km/s) |
|
||||
| `eci_vz(eci_position)` | `float8` | Z velocity (km/s) |
|
||||
| `eci_speed(eci_position)` | `float8` | Velocity magnitude (km/s) |
|
||||
| `eci_altitude(eci_position)` | `float8` | Geocentric altitude (km) |
|
||||
|
||||
### Topocentric Accessors
|
||||
|
||||
| Function | Returns | Description |
|
||||
|----------|---------|-------------|
|
||||
| `topo_azimuth(topocentric)` | `float8` | Azimuth in degrees (0=N, 90=E, 180=S, 270=W) |
|
||||
| `topo_elevation(topocentric)` | `float8` | Elevation in degrees (0=horizon, 90=zenith) |
|
||||
| `topo_range(topocentric)` | `float8` | Slant range (km) |
|
||||
| `topo_range_rate(topocentric)` | `float8` | Range rate (km/s, positive = receding) |
|
||||
|
||||
### Pass Prediction
|
||||
|
||||
| Function | Returns | Description |
|
||||
|----------|---------|-------------|
|
||||
| `next_pass(tle, observer, timestamptz)` | `pass_event` | Next pass over observer. Searches up to 7 days. |
|
||||
| `predict_passes(tle, observer, start, stop [, min_el])` | `SETOF pass_event` | All passes in a time window. Optional minimum elevation (degrees). |
|
||||
| `pass_visible(tle, observer, start, stop)` | `boolean` | True if any pass occurs in the time window. |
|
||||
|
||||
### Pass Event Accessors
|
||||
|
||||
| Function | Returns | Description |
|
||||
|----------|---------|-------------|
|
||||
| `pass_aos_time(pass_event)` | `timestamptz` | Acquisition of signal time |
|
||||
| `pass_max_el_time(pass_event)` | `timestamptz` | Maximum elevation time |
|
||||
| `pass_los_time(pass_event)` | `timestamptz` | Loss of signal time |
|
||||
| `pass_max_elevation(pass_event)` | `float8` | Maximum elevation (degrees) |
|
||||
| `pass_aos_azimuth(pass_event)` | `float8` | AOS azimuth (degrees, 0=N) |
|
||||
| `pass_los_azimuth(pass_event)` | `float8` | LOS azimuth (degrees, 0=N) |
|
||||
| `pass_duration(pass_event)` | `interval` | Pass duration (LOS - AOS) |
|
||||
|
||||
### Operators
|
||||
|
||||
| Operator | Operands | Description |
|
||||
|----------|----------|-------------|
|
||||
| `&&` | `tle, tle` | Altitude band overlap. Necessary (not sufficient) condition for conjunction. |
|
||||
| `<->` | `tle, tle` | Minimum altitude-band separation in km. Returns 0 if bands overlap. |
|
||||
|
||||
Both operators are supported by the GiST `tle_ops` operator class for indexed scans.
|
||||
| ID | Planet | | Galilean (0-3) | Saturn (0-7) | Uranus (0-4) | Mars (0-1) |
|
||||
|----|--------|-|----------------|--------------|--------------|------------|
|
||||
| 1 | Mercury | | 0: Io | 0: Mimas | 0: Miranda | 0: Phobos |
|
||||
| 2 | Venus | | 1: Europa | 1: Enceladus | 1: Ariel | 1: Deimos |
|
||||
| 3 | Earth | | 2: Ganymede | 2: Tethys | 2: Umbriel | |
|
||||
| 4 | Mars | | 3: Callisto | 3: Dione | 3: Titania | |
|
||||
| 5 | Jupiter | | | 4: Rhea | 4: Oberon | |
|
||||
| 6 | Saturn | | | 5: Titan | | |
|
||||
| 7 | Uranus | | | 6: Iapetus | | |
|
||||
| 8 | Neptune | | | 7: Hyperion | | |
|
||||
|
||||
## GiST Indexing
|
||||
|
||||
The `tle_ops` operator class indexes TLEs by their altitude band (perigee to apogee).
|
||||
This provides fast filtering for conjunction screening: only pairs whose altitude
|
||||
bands overlap can possibly be close to each other.
|
||||
The `tle_ops` operator class indexes TLEs by altitude band for conjunction screening:
|
||||
|
||||
```sql
|
||||
-- Create the index
|
||||
CREATE INDEX idx_tle_alt ON satellites USING gist (tle);
|
||||
CREATE INDEX ON satellites USING gist (tle);
|
||||
|
||||
-- The && operator triggers index scans
|
||||
EXPLAIN SELECT a.name, b.name
|
||||
-- Find objects in overlapping altitude shells
|
||||
SELECT a.name, b.name
|
||||
FROM satellites a, satellites b
|
||||
WHERE a.tle && b.tle AND a.norad_id < b.norad_id;
|
||||
|
||||
-- KNN ordering by altitude-band distance
|
||||
SELECT name, tle <-> (SELECT tle FROM satellites WHERE norad_id = 25544) AS sep
|
||||
FROM satellites
|
||||
ORDER BY tle <-> (SELECT tle FROM satellites WHERE norad_id = 25544)
|
||||
-- K-nearest-neighbor by altitude separation
|
||||
SELECT name, round((tle <-> iss.tle)::numeric, 0) AS alt_sep_km
|
||||
FROM satellites, (SELECT tle FROM satellites WHERE norad_id = 25544) iss
|
||||
ORDER BY tle <-> iss.tle
|
||||
LIMIT 20;
|
||||
```
|
||||
|
||||
The index reduces conjunction candidate pairs from O(n^2) to the set of objects with
|
||||
intersecting altitude bands, which is then refined by computing actual `tle_distance()`
|
||||
at a specific time.
|
||||
## Performance
|
||||
|
||||
## Geodetic Constants
|
||||
Measured on PostgreSQL 17, single backend:
|
||||
|
||||
TLEs are mean elements fitted using WGS-72 constants. Using WGS-84 constants for
|
||||
propagation introduces kilometer-scale position errors because the elements absorb
|
||||
geodetic model biases during the fitting process.
|
||||
|
||||
pg_orbit enforces this:
|
||||
- **Propagation (SGP4/SDP4):** WGS-72 constants only (mu, ae, J2, J3, J4, ke)
|
||||
- **Coordinate output (geodetic, topocentric):** WGS-84 (a=6378.137 km, f=1/298.257223563)
|
||||
|
||||
These are not interchangeable. Mixing them is a silent accuracy loss.
|
||||
|
||||
## Building from Source
|
||||
|
||||
```bash
|
||||
# Build (requires pg_config in PATH)
|
||||
make
|
||||
|
||||
# Install to PostgreSQL extension directory
|
||||
sudo make install
|
||||
|
||||
# Run regression tests against a live database
|
||||
make installcheck
|
||||
```
|
||||
|
||||
Override `pg_config` location if needed:
|
||||
|
||||
```bash
|
||||
make PG_CONFIG=/usr/lib/postgresql/16/bin/pg_config
|
||||
sudo make PG_CONFIG=/usr/lib/postgresql/16/bin/pg_config install
|
||||
```
|
||||
|
||||
### Project Layout
|
||||
|
||||
```
|
||||
pg_orbit.control Extension metadata (version 0.1.0)
|
||||
Makefile PGXS build
|
||||
sql/
|
||||
pg_orbit--0.1.0.sql Type, function, operator, and GiST definitions
|
||||
src/
|
||||
pg_orbit.c PG_MODULE_MAGIC entry point
|
||||
tle_type.c TLE input/output/binary/accessors
|
||||
eci_type.c ECI position type
|
||||
observer_type.c Observer type with flexible parsing
|
||||
sgp4_funcs.c SGP4 propagation and distance
|
||||
coord_funcs.c Coordinate transforms (TEME/geodetic/topocentric)
|
||||
pass_funcs.c Pass prediction (next_pass, predict_passes)
|
||||
gist_tle.c GiST operator class for altitude-band indexing
|
||||
types.h Shared struct definitions and constants
|
||||
lib/
|
||||
sat_code/ Bill Gray's SGP4/SDP4 (MIT, git submodule)
|
||||
test/
|
||||
sql/ Regression test SQL
|
||||
expected/ Expected output
|
||||
data/
|
||||
vallado_518.csv 518 verification test vectors (Vallado et al.)
|
||||
```
|
||||
| Operation | Count | Time | Rate |
|
||||
|---|---|---|---|
|
||||
| TLE propagation (SGP4) | 12,000 | 17ms | 706K/sec |
|
||||
| Planet observation (VSOP87) | 875 | 57ms | 15.4K/sec |
|
||||
| Moon observation (Galilean) | 1,000 | 63ms | 15.9K/sec |
|
||||
| Star observation | 500 | 0.7ms | 714K/sec |
|
||||
| Lambert transfer solve | 100 | 0.1ms | 800K/sec |
|
||||
| Pork chop plot (150x150) | 22,500 | 8.3s | 2.7K/sec |
|
||||
|
||||
## Testing
|
||||
|
||||
pg_orbit uses the standard PostgreSQL regression test framework.
|
||||
11 regression test suites covering all domains:
|
||||
|
||||
```bash
|
||||
make installcheck
|
||||
make installcheck PG_CONFIG=/usr/bin/pg_config
|
||||
```
|
||||
|
||||
Test categories:
|
||||
Tests: TLE parsing, SGP4/SDP4 propagation, coordinate transforms, pass prediction,
|
||||
GiST indexing, convenience functions, star observation, Keplerian propagation,
|
||||
planet observation, moon observation, and Lambert transfers.
|
||||
|
||||
| Suite | Coverage |
|
||||
|-------|----------|
|
||||
| `tle_parse` | TLE input/output round-trip, malformed input rejection |
|
||||
| `sgp4_propagate` | Vallado 518 test vectors, deep-space and high-eccentricity edge cases |
|
||||
| `coord_transforms` | TEME to geodetic, TEME to topocentric accuracy |
|
||||
| `pass_prediction` | Known ISS passes, polar and retrograde orbits |
|
||||
| `gist_index` | Index scan vs. sequential scan result equivalence |
|
||||
## Documentation
|
||||
|
||||
The Vallado 518 test vectors are the standard SGP4 verification dataset. Each row
|
||||
contains a NORAD ID, minutes since epoch, and expected position/velocity. All 518
|
||||
must pass to machine epsilon.
|
||||
Full documentation at the [pg_orbit docs site](https://pg-orbit.supported.systems),
|
||||
built with [Starlight](https://starlight.astro.build). Includes guides, workflow
|
||||
translations (from Skyfield, JPL Horizons, GMAT, Radio Jupiter Pro), complete
|
||||
function reference, architecture notes, and benchmarks.
|
||||
|
||||
## What pg_orbit Is Not
|
||||
|
||||
**Not a GUI.** Use Stellarium, GPredict, or STK for visualization.
|
||||
|
||||
**Not sub-arcsecond.** VSOP87 gives ~1 arcsecond — good for observation planning,
|
||||
not for dish pointing at GHz frequencies. For that, use SPICE or Skyfield with DE441.
|
||||
|
||||
**Not a TLE source.** Bring your own from Space-Track, CelesTrak, or any provider.
|
||||
|
||||
**Not a replacement for SPICE.** No BSP kernels, no aberration corrections at IAU 2000A
|
||||
level. pg_orbit trades those last few milliarcseconds for SQL-speed computation joined
|
||||
with your existing data.
|
||||
|
||||
**Not a full mission design tool.** The Lambert solver handles ballistic two-body
|
||||
transfers. For low-thrust, gravity assists, or multi-body optimization, use GMAT.
|
||||
|
||||
## Upgrading from v0.1.0
|
||||
|
||||
```sql
|
||||
ALTER EXTENSION pg_orbit UPDATE TO '0.2.0';
|
||||
```
|
||||
|
||||
Adds all solar system functions while preserving existing TLE data and satellite functions.
|
||||
|
||||
## License
|
||||
|
||||
[PostgreSQL License](LICENSE). Copyright (c) 2025, Ryan Malloy.
|
||||
|
||||
The bundled sat_code library is separately licensed under the MIT license.
|
||||
The bundled [sat_code](https://github.com/Bill-Gray/sat_code) library is separately
|
||||
licensed under the MIT license.
|
||||
|
||||
32
docs/Makefile
Normal file
32
docs/Makefile
Normal file
@ -0,0 +1,32 @@
|
||||
.PHONY: dev prod build down logs clean restart
|
||||
|
||||
COMPOSE = docker compose
|
||||
|
||||
# Start in development mode with hot-reload
|
||||
dev:
|
||||
$(COMPOSE) -f docker-compose.yml -f docker-compose.dev.yml up -d --build
|
||||
@$(COMPOSE) -f docker-compose.yml -f docker-compose.dev.yml logs -f
|
||||
|
||||
# Start in production mode (static build served by Caddy)
|
||||
prod:
|
||||
$(COMPOSE) up -d --build
|
||||
@$(COMPOSE) logs --tail=30
|
||||
|
||||
# Build image without starting
|
||||
build:
|
||||
$(COMPOSE) build
|
||||
|
||||
# Stop services
|
||||
down:
|
||||
$(COMPOSE) down
|
||||
|
||||
# Tail logs
|
||||
logs:
|
||||
$(COMPOSE) logs -f
|
||||
|
||||
# Stop and remove volumes
|
||||
clean:
|
||||
$(COMPOSE) down -v --remove-orphans
|
||||
|
||||
# Restart in production mode
|
||||
restart: down prod
|
||||
@ -114,6 +114,16 @@ export default defineConfig({
|
||||
|
||||
vite: {
|
||||
plugins: [tailwindcss()],
|
||||
server: {
|
||||
host: "0.0.0.0",
|
||||
...(process.env.VITE_HMR_HOST && {
|
||||
hmr: {
|
||||
host: process.env.VITE_HMR_HOST,
|
||||
protocol: "wss",
|
||||
clientPort: 443,
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
|
||||
telemetry: false,
|
||||
|
||||
21
docs/docker-compose.dev.yml
Normal file
21
docs/docker-compose.dev.yml
Normal file
@ -0,0 +1,21 @@
|
||||
services:
|
||||
docs:
|
||||
build:
|
||||
target: development
|
||||
volumes:
|
||||
- ./src:/app/src
|
||||
- ./public:/app/public
|
||||
- ./astro.config.mjs:/app/astro.config.mjs
|
||||
- ./package.json:/app/package.json
|
||||
environment:
|
||||
- VITE_HMR_HOST=${VITE_HMR_HOST:-pg-orbit.supported.systems}
|
||||
labels:
|
||||
# WebSocket / HMR support for dev hot-reload
|
||||
caddy.reverse_proxy.flush_interval: "-1"
|
||||
caddy.reverse_proxy.transport: http
|
||||
caddy.reverse_proxy.transport.read_timeout: "0"
|
||||
caddy.reverse_proxy.transport.write_timeout: "0"
|
||||
caddy.reverse_proxy.transport.keepalive: 5m
|
||||
caddy.reverse_proxy.transport.keepalive_idle_conns: "10"
|
||||
caddy.reverse_proxy.stream_timeout: 24h
|
||||
caddy.reverse_proxy.stream_close_delay: 5s
|
||||
22
docs/docker-compose.yml
Normal file
22
docs/docker-compose.yml
Normal file
@ -0,0 +1,22 @@
|
||||
services:
|
||||
docs:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
target: production
|
||||
container_name: pg-orbit-docs
|
||||
restart: unless-stopped
|
||||
expose:
|
||||
- "3000"
|
||||
networks:
|
||||
- caddy
|
||||
environment:
|
||||
- ASTRO_TELEMETRY_DISABLED=1
|
||||
labels:
|
||||
caddy: pg-orbit.supported.systems
|
||||
caddy.reverse_proxy: "{{upstreams 3000}}"
|
||||
caddy.encode: gzip
|
||||
|
||||
networks:
|
||||
caddy:
|
||||
external: true
|
||||
Loading…
x
Reference in New Issue
Block a user