Eliminates the seed TLE requirement for topocentric fitting by
computing an initial orbit estimate from 3 well-spaced observations
using the Gibbs method. ECI fitting retains the single-observation
r,v approach (exact for two-body) with Gibbs as fallback.
Extend od_observation_t with observer_idx so each observation can
reference a different ground station. Config now holds an array of
observers instead of a single pointer. The existing single-observer
tle_from_topocentric() is unchanged (sets observer_idx=0 for all obs).
New overload: tle_from_topocentric(topo[], ts[], observer[], int4[], ...)
accepts parallel observer_ids array indexing into the observers array.
PG function overloading resolves by argument types.
Tests 9-11: two-station fit converges, single-station via multi-observer
API matches, out-of-range observer_id raises error.
Scale step limits by a trust-region factor that halves on divergence
(RMS increases > 1%) and relaxes toward full step on good convergence
(RMS decreases > 10%). Prevents oscillation with poor initial guesses
without affecting well-seeded fits. Also stores SVD condition number
for diagnostic use in upcoming covariance output.
Existing 8 OD regression tests + 67 standalone math tests unaffected
(adaptive_factor starts at 1.0, round-trip tests never trigger
divergence).
Batch weighted least-squares differential correction using equinoctial
elements, LAPACK dgelss_() for SVD solve, vendored SGP4/SDP4 as the
propagation engine. Per Vallado & Crawford (2008) AIAA 2008-6770.
New SQL functions:
- tle_from_eci(): fit TLE from ECI position/velocity ephemeris
- tle_from_topocentric(): fit TLE from az/el/range observations
- tle_fit_residuals(): per-observation position residuals diagnostic
Solver features: 6-state (orbital) or 7-state (+ B*) fitting,
equinoctial elements for singularity-free optimization, tiered step
limiting, Brouwer/Kozai Newton-Raphson conversion, auto initial guess
from first ECI observation when no seed TLE provided.
Tested: 8 regression tests (LEO/MEO/near-circular round-trips,
B* recovery, topocentric, seedless, error handling, diagnostics),
67 standalone math unit tests, all 14 suites pass.
Shell script drives the Dockerfile builder stage across PG versions,
capturing pass/fail + timing per version. Makefile targets: test-matrix,
test-pg%, test-matrix-clean. Also runs standalone DE reader test in the
builder stage to catch compiler-version regressions.
Fix pork chop grid test: add ORDER BY to CROSS JOIN (optimizer chooses
different join nesting across PG versions, reordering rows).
An existing product called PG Orbit (a mobile PostgreSQL client)
creates a naming conflict. pg_orrery — a database orrery built from
Keplerian parameters and SQL instead of brass gears.
Build system: control file, Makefile, Dockerfile, docker init script.
C source: GUC prefix, PG_FUNCTION_INFO_V1 symbol, header guards,
ereport prefixes, comments across ~30 files including vendored SGP4.
SQL: all 5 install/migration scripts, function name pg_orrery_ephemeris_info.
Tests: 9 SQL suites, 8 expected outputs, standalone DE reader test.
Documentation: CLAUDE.md, README.md, DESIGN.md, Starlight site infra,
36 MDX pages, OG renderer, logo SVG, docker-compose, agent threads.
All 13 regression suites pass. Docs site builds (37 pages).
Replace the sat_code git submodule (lib/sat_code/) with vendored
sources in src/sgp4/. The upstream .cpp files are renamed to .c —
the code is valid C99 with zero C++ features. This eliminates the
g++ and -lstdc++ build dependencies.
Adds 518 Vallado test vectors (AIAA 2006-6753-Rev1) as a 13th
regression suite to verify byte-identical numerical output.
Updates all documentation (CLAUDE.md, DESIGN.md, 11 MDX pages,
Dockerfile) to reflect the new layout and pure-C compilation.
Standalone test (test/test_de_reader.c): generates a synthetic 12KB
DE binary with known Chebyshev coefficients, exercises header parsing,
polynomial evaluation at 5 domain points, Earth derivation via
center=99, Moon geocentric, layout validation, range/body error paths,
NAN sentinel, and garbage file rejection. 55 tests, no PG dependency.
Makefile: add `make test-de-reader` target; also includes the earlier
sat_code C++ -> pure C sgp4 migration that was missed from prior commits.
astro_math.h: document that ecliptic_to_equatorial/equatorial_to_ecliptic
are NOT safe for aliased (in-place) calls — equ[1] is written before
equ[2] reads ecl[1]. The vendored sgp4/sdp4.c has separate in-place
versions using a temp variable.
Clean-room DE binary reader (~400 lines C) with Chebyshev/Clenshaw
evaluation — no GPL dependency on jpl_eph. Per-backend lazy
initialization preserves PARALLEL SAFE. Existing VSOP87/ELP82B
functions stay IMMUTABLE; new _de() variants are STABLE with
automatic fallback to compiled-in ephemerides on any DE failure.
Implementation:
- de_reader.c: header parse, record seek, Clenshaw recurrence
- eph_provider.c: GUC (pg_orbit.ephemeris_path), lazy init,
ICRS-to-ecliptic frame rotation, on_proc_exit cleanup
- de_funcs.c: 11 new SQL functions (_de variants + diagnostics)
- Constant chain of custody rules 6-8 (frame rotation,
same-provider, AU consistency)
Extract observe_from_geocentric() to astro_math.h for shared use
by planet_funcs.c, moon_funcs.c, and de_funcs.c.
57 → 68 functions, 11 → 12 regression test suites, all passing.
Add Universal Variable Lambert solver for computing transfer orbits
between any two planets. Enables pork chop plot generation as SQL:
SELECT dep_date, arr_date, lambert_c3(3, 4, dep_date, arr_date)
FROM generate_series(...) dep CROSS JOIN generate_series(...) arr;
New functions:
- lambert_transfer(dep_body, arr_body, dep_time, arr_time) → RECORD
Returns C3 departure/arrival (km^2/s^2), v_infinity (km/s),
time of flight (days), and transfer orbit SMA (AU).
- lambert_c3(dep_body, arr_body, dep_time, arr_time) → float8
Convenience: departure C3 only, NULL on solver failure.
The solver uses Stumpff functions for unified elliptic/parabolic/hyperbolic
handling, with Newton-Raphson iteration and bisection fallback.
Each solve is sub-millisecond; PARALLEL SAFE for batch computation.
All 11 regression tests pass.
Add observation functions for 19 planetary moons across four systems:
- Galilean moons (Io, Europa, Ganymede, Callisto) via clean-room L1.2 theory
- Saturn moons (Mimas through Hyperion) via TASS 1.7
- Uranus moons (Miranda through Oberon) via GUST86
- Mars moons (Phobos, Deimos) via MarsSat
Add Jupiter decametric radio burst prediction for Radio JOVE operators:
- io_phase_angle() — Io orbital phase from superior conjunction
- jupiter_cml() — System III Central Meridian Longitude with light-time correction
- jupiter_burst_probability() — Carr et al. (1983) source regions A, B, C, D
L1.2 Galilean theory is a clean-room MIT implementation from the published
IMCCE FORTRAN coefficients. All other ephemeris libraries are MIT-licensed
extractions from Stellarium with static caching removed for PARALLEL SAFE.
All 10 regression tests pass. Extension .so grows from 2.4MB to 2.5MB.
Phase 1 — Stars, comets, Keplerian propagation:
- star_observe() / star_observe_safe(): fixed star alt/az via IAU 1976
precession, equatorial-to-horizontal transform
- kepler_propagate(): two-body Keplerian orbit propagation for
elliptic, parabolic, and hyperbolic orbits
- comet_observe(): observe comets/asteroids from orbital elements
- heliocentric type: ecliptic J2000 position (x, y, z in AU)
Phase 2 — VSOP87 planets, ELP82B Moon, Sun:
- planet_heliocentric(): VSOP87 heliocentric ecliptic J2000 positions
for Mercury through Neptune (Bretagnon & Francou, MIT)
- planet_observe(): full observation pipeline for any planet
- sun_observe(): Sun position from negated Earth VSOP87
- moon_observe(): ELP2000-82B lunar position (Chapront-Touzé, MIT)
- Clean-room precession (IAU 2006) and sidereal time (IERS 2010)
- elliptic_to_rectangular utility (Stellarium, MIT)
All Stellarium extractions are MIT-licensed, thread-safe (static
caching removed for PARALLEL SAFE), zero external data files.
All 9 regression tests pass (90ms total).
The 1-D altitude-band index only pruned ~25% of the 22k satellite
catalog (eliminates MEO/GEO/HEO but 75% is LEO). Adding inclination
as a second indexed dimension prunes an additional ~40% of remaining
candidates — objects in equatorial or low-inclination orbits that
geometrically cannot pass over the observer's latitude.
Key changes:
- tle_alt_range (16 bytes) → tle_orbital_key (32 bytes) with
inc_low/inc_high fields
- All 8 GiST support functions updated for 2-D bounding boxes
- Penalty uses margin (half-perimeter) not area to avoid degeneracy
when leaf entries have zero-width inclination ranges
- Picksplit selects split dimension by normalized spread
- && operator now checks altitude AND inclination overlap
- <-> operator remains altitude-only (conjunction screening is
altitude-dominant)
- SQL operator comments updated for 2-D semantics
- Test adds Equatorial-LEO satellite at ISS altitude but 5° inclination
to validate inclination-based pruning
Implements 5 new C functions requested by the Craft (Astrolock) API team:
- tle_from_lines(text, text): two-argument TLE constructor
- observer_from_geodetic(float8, float8, float8): numeric observer constructor
- observe(tle, observer, timestamptz): single-call propagate + topocentric
- sgp4_propagate_safe(tle, timestamptz): returns NULL on propagation error
- observe_safe(tle, observer, timestamptz): returns NULL on propagation error
Refactors do_propagate() into safe/unsafe variants to support NULL returns.
Adds regression test (convenience.sql) covering all new functions including
an equivalence test verifying observe() matches the manual two-step pipeline.
All 6 regression tests pass.
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.