diff --git a/Makefile b/Makefile index 71630f4..6faaa77 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,9 @@ DATA = sql/pg_orrery--0.1.0.sql sql/pg_orrery--0.2.0.sql sql/pg_orrery--0.1.0--0 sql/pg_orrery--0.4.0.sql sql/pg_orrery--0.3.0--0.4.0.sql \ sql/pg_orrery--0.5.0.sql sql/pg_orrery--0.4.0--0.5.0.sql \ sql/pg_orrery--0.6.0.sql sql/pg_orrery--0.5.0--0.6.0.sql \ - sql/pg_orrery--0.7.0.sql sql/pg_orrery--0.6.0--0.7.0.sql + sql/pg_orrery--0.7.0.sql sql/pg_orrery--0.6.0--0.7.0.sql \ + sql/pg_orrery--0.8.0.sql sql/pg_orrery--0.7.0--0.8.0.sql \ + sql/pg_orrery--0.9.0.sql sql/pg_orrery--0.8.0--0.9.0.sql # Our extension C sources OBJS = src/pg_orrery.o src/tle_type.o src/eci_type.o src/observer_type.o \ @@ -18,7 +20,10 @@ OBJS = src/pg_orrery.o src/tle_type.o src/eci_type.o src/observer_type.o \ src/lambert.o src/transfer_funcs.o \ src/de_reader.o src/eph_provider.o src/de_funcs.o \ src/od_math.o src/od_iod.o src/od_solver.o src/od_funcs.o \ - src/spgist_tle.o + src/spgist_tle.o \ + src/orbital_elements_type.o \ + src/equatorial_funcs.o \ + src/refraction_funcs.o # Vendored SGP4/SDP4 sources (pure C, from Bill Gray's sat_code, MIT license) SGP4_DIR = src/sgp4 @@ -33,7 +38,7 @@ OBJS += $(SGP4_OBJS) # Regression tests REGRESS = tle_parse sgp4_propagate coord_transforms pass_prediction gist_index convenience \ star_observe kepler_comet planet_observe moon_observe lambert_transfer \ - de_ephemeris od_fit spgist_tle vallado_518 + de_ephemeris od_fit spgist_tle orbital_elements equatorial refraction vallado_518 REGRESS_OPTS = --inputdir=test # Pure C — no C++ runtime needed. LAPACK for OD solver (dgelss_). diff --git a/bench/tle_archives.tar.gz b/bench/tle_archives.tar.gz index 3fb4815..fbac9b5 100644 Binary files a/bench/tle_archives.tar.gz and b/bench/tle_archives.tar.gz differ diff --git a/docs/public/llms-full.txt b/docs/public/llms-full.txt index 41837eb..577ee23 100644 --- a/docs/public/llms-full.txt +++ b/docs/public/llms-full.txt @@ -1,6 +1,6 @@ # pg_orrery — Complete LLM Reference -> Celestial mechanics types and functions for PostgreSQL. Native C extension (v0.8.0) with 82 SQL functions, 8 custom types + 1 composite, GiST/SP-GiST indexing. All functions PARALLEL SAFE. +> Celestial mechanics types and functions for PostgreSQL. Native C extension (v0.9.0) with 106 SQL functions, 9 custom types + 1 composite, GiST/SP-GiST indexing. All functions PARALLEL SAFE. - Source: https://git.supported.systems/warehack.ing/pg_orrery - Docs: https://pg-orrery.warehack.ing @@ -110,6 +110,17 @@ SELECT oe_from_mpc('00001 3.52 0.15 K249V 14.81198 ...fixed-width MPC line Accessors: `oe_epoch` (JD), `oe_perihelion` (AU), `oe_eccentricity`, `oe_inclination` (degrees), `oe_arg_perihelion` (degrees), `oe_raan` (degrees), `oe_tp` (JD), `oe_h_mag` (NaN if unknown), `oe_g_slope` (NaN if unknown), `oe_semi_major_axis` (AU, NULL if e≥1), `oe_period_years` (NULL if e≥1). +### equatorial (24 bytes) + +Apparent equatorial coordinates of date: RA, Dec, distance. Solar system bodies: J2000 precessed via IAU 1976. Satellites: TEME frame (~of-date to ~arcsecond). + +```sql +-- Output format: (ra_hours, dec_degrees, distance_km) +-- Example: (4.29220000,20.60000000,885412345.678) +``` + +Accessors: `eq_ra(equatorial) → float8` (hours [0,24)), `eq_dec(equatorial) → float8` (degrees [-90,90]), `eq_distance(equatorial) → float8` (km; 0 for stars without parallax). + ### observer_window (composite) Query parameter bundle for SP-GiST visibility cone operator. @@ -156,7 +167,7 @@ Fields: `obs` (observer), `t_start` (timestamptz), `t_end` (timestamptz), `min_e ## Functions by Domain -### Satellite — SGP4/SDP4 Propagation (22 functions) +### Satellite — SGP4/SDP4 Propagation (25 functions) ``` sgp4_propagate(tle, timestamptz) → eci_position IMMUTABLE @@ -166,6 +177,8 @@ tle_distance(tle, tle, timestamptz) → float8 IMMUTAB eci_to_geodetic(eci_position, timestamptz) → geodetic IMMUTABLE eci_to_topocentric(eci_position, observer, timestamptz) → topocentric IMMUTABLE +eci_to_equatorial(eci_position, observer, timestamptz) → equatorial IMMUTABLE -- topocentric RA/Dec (parallax-corrected) +eci_to_equatorial_geo(eci_position, timestamptz) → equatorial IMMUTABLE -- geocentric RA/Dec (observer-independent) subsatellite_point(tle, timestamptz) → geodetic IMMUTABLE ground_track(tle, start, end, step) → SETOF (t, lat, lon, alt) IMMUTABLE @@ -174,6 +187,7 @@ observe_safe(tle, observer, timestamptz) → topocentric IMMUTAB next_pass(tle, observer, timestamptz) → pass_event STABLE -- searches up to 7 days predict_passes(tle, observer, start, end, min_el DEFAULT 0.0) → SETOF pass_event STABLE +predict_passes_refracted(tle, observer, start, end, min_el DEFAULT 0.0) → SETOF pass_event STABLE -- refracted horizon (-0.569°) pass_visible(tle, observer, start, end) → boolean STABLE tle_from_lines(text, text) → tle IMMUTABLE @@ -182,13 +196,24 @@ observer_from_geodetic(lat_deg, lon_deg, alt_m DEFAULT 0.0) → observer IMMUTAB TLE accessors (15): `tle_epoch`, `tle_norad_id`, `tle_inclination`, `tle_eccentricity`, `tle_raan`, `tle_arg_perigee`, `tle_mean_anomaly`, `tle_mean_motion`, `tle_bstar`, `tle_period`, `tle_age`, `tle_perigee`, `tle_apogee`, `tle_intl_desig`, `tle_from_lines`. -### Solar System — VSOP87 + ELP2000-82B (5 functions) +### Solar System — VSOP87 + ELP2000-82B (14 functions) ``` planet_heliocentric(body_id int4, timestamptz) → heliocentric IMMUTABLE -- IDs 0-8 planet_observe(body_id int4, observer, timestamptz) → topocentric IMMUTABLE -- IDs 1-8 sun_observe(observer, timestamptz) → topocentric IMMUTABLE moon_observe(observer, timestamptz) → topocentric IMMUTABLE + +-- Equatorial RA/Dec (apparent, of date) +planet_equatorial(body_id int4, timestamptz) → equatorial IMMUTABLE -- geocentric +sun_equatorial(timestamptz) → equatorial IMMUTABLE +moon_equatorial(timestamptz) → equatorial IMMUTABLE + +-- Light-time corrected (body at retarded time, Earth at observation time) +planet_observe_apparent(body_id int4, observer, timestamptz) → topocentric IMMUTABLE +sun_observe_apparent(observer, timestamptz) → topocentric IMMUTABLE +planet_equatorial_apparent(body_id int4, timestamptz) → equatorial IMMUTABLE +moon_equatorial_apparent(timestamptz) → equatorial IMMUTABLE ``` ### Planetary Moons (4 functions) @@ -200,16 +225,21 @@ uranus_moon_observe(moon_id int4, observer, timestamptz) → topocentric IMMUTAB mars_moon_observe(moon_id int4, observer, timestamptz) → topocentric IMMUTABLE -- MarsSat, IDs 0-1 ``` -### Stars (2 functions) +### Stars (5 functions) ``` star_observe(ra_hours float8, dec_degrees float8, observer, timestamptz) → topocentric IMMUTABLE star_observe_safe(ra_hours float8, dec_degrees float8, observer, timestamptz) → topocentric IMMUTABLE -- NULL on error +star_equatorial(ra_hours, dec_degrees, timestamptz) → equatorial IMMUTABLE -- precesses J2000 to date + +-- Proper motion (Hipparcos/Gaia convention: pm_ra = mu_alpha * cos(delta) in mas/yr) +star_observe_pm(ra_h, dec_deg, pm_ra_masyr, pm_dec_masyr, parallax_mas, rv_kms, observer, timestamptz) → topocentric IMMUTABLE +star_equatorial_pm(ra_h, dec_deg, pm_ra_masyr, pm_dec_masyr, parallax_mas, rv_kms, timestamptz) → equatorial IMMUTABLE ``` -RA in hours [0,24), Dec in degrees [-90,90]. Range returned as 0 (infinite distance). +RA in hours [0,24), Dec in degrees [-90,90]. Range returned as 0 (infinite distance) unless parallax > 0 in _pm variants. -### Comets & Asteroids — Keplerian + MPC (5 functions) +### Comets & Asteroids — Keplerian + MPC (9 functions) ``` kepler_propagate(q_au, eccentricity, inc_deg, arg_peri_deg, raan_deg, perihelion_jd, timestamptz) → heliocentric IMMUTABLE @@ -217,6 +247,9 @@ comet_observe(q_au, e, inc, omega, Omega, tp_jd, earth_x, earth_y, earth_z, obse oe_from_mpc(text) → orbital_elements IMMUTABLE -- parse MPC MPCORB.DAT line small_body_heliocentric(orbital_elements, timestamptz) → heliocentric IMMUTABLE small_body_observe(orbital_elements, observer, timestamptz) → topocentric IMMUTABLE -- auto-fetches Earth via VSOP87 +small_body_equatorial(orbital_elements, timestamptz) → equatorial IMMUTABLE -- geocentric RA/Dec +small_body_observe_apparent(orbital_elements, observer, timestamptz) → topocentric IMMUTABLE -- light-time corrected +small_body_equatorial_apparent(orbital_elements, timestamptz) → equatorial IMMUTABLE -- light-time corrected RA/Dec ``` orbital_elements accessors (11): `oe_epoch`, `oe_perihelion`, `oe_eccentricity`, `oe_inclination`, `oe_arg_perihelion`, `oe_raan`, `oe_tp`, `oe_h_mag`, `oe_g_slope`, `oe_semi_major_axis`, `oe_period_years`. @@ -239,7 +272,18 @@ lambert_c3(dep_body int4, arr_body int4, dep_time, arr_time) → float8 IMMUTA Body IDs 1–8 (Mercury–Neptune). C3 in km²/s², v_inf in km/s, TOF in days, SMA in AU. -### DE Ephemeris — Optional High-Precision (11 functions) +### Atmospheric Refraction — Bennett 1982 (4 functions) + +``` +atmospheric_refraction(elevation_deg float8) → float8 IMMUTABLE -- degrees; standard atmosphere P=1010, T=10°C +atmospheric_refraction_ext(elevation_deg, pressure_mbar, temp_celsius) → float8 IMMUTABLE -- with Meeus P/T correction +topo_elevation_apparent(topocentric) → float8 IMMUTABLE -- geometric + refraction, in degrees +predict_passes_refracted(tle, observer, start, end, min_el DEFAULT 0.0) → SETOF pass_event STABLE -- horizon at -0.569° geometric +``` + +Bennett formula: `R = 1/tan(h + 7.31/(h + 4.4))` arcminutes. Domain guard: clamps at -1°, returns 0.0 below. At horizon (0°) refraction is ~0.57°, meaning satellites become visible ~35 seconds earlier. + +### DE Ephemeris — Optional High-Precision (13 functions) All _de() functions fall back to VSOP87/ELP2000-82B when DE is unavailable. All STABLE (external file dependency). @@ -254,6 +298,8 @@ galilean_observe_de(moon_id, observer, timestamptz) → topocentric STABLE saturn_moon_observe_de(moon_id, observer, timestamptz) → topocentric STABLE uranus_moon_observe_de(moon_id, observer, timestamptz) → topocentric STABLE mars_moon_observe_de(moon_id, observer, timestamptz) → topocentric STABLE +planet_equatorial_de(body_id int4, timestamptz) → equatorial STABLE -- geocentric RA/Dec via DE +moon_equatorial_de(timestamptz) → equatorial STABLE -- geocentric RA/Dec via DE pg_orrery_ephemeris_info() → (provider, file_path, start_jd, end_jd, version, au_km) STABLE ``` @@ -431,6 +477,42 @@ SELECT (tle_from_eci( -- Returns: fitted_tle, iterations, rms_final, rms_initial, status, condition_number, covariance, nstate ``` +### Get RA/Dec for telescope GoTo + +```sql +-- Planet RA/Dec (apparent, of date — what telescope mounts expect) +SELECT eq_ra(planet_equatorial(5, NOW())) AS jupiter_ra_hours, + eq_dec(planet_equatorial(5, NOW())) AS jupiter_dec_deg; + +-- With light-time correction (Jupiter light-travel ~35-52 min) +SELECT eq_ra(planet_equatorial_apparent(5, NOW())) AS ra_h, + eq_dec(planet_equatorial_apparent(5, NOW())) AS dec_deg; + +-- Star with proper motion (Barnard's Star from Hipparcos/Gaia catalog) +SELECT eq_ra(star_equatorial_pm(17.963472, 4.6933, -798.58, 10328.12, 545.4, -110.51, NOW())) AS ra_h, + eq_dec(star_equatorial_pm(17.963472, 4.6933, -798.58, 10328.12, 545.4, -110.51, NOW())) AS dec_deg; +``` + +### Apparent elevation with atmospheric refraction + +```sql +-- Compare geometric vs apparent elevation +SELECT topo_elevation(obs) AS geometric_el, + topo_elevation_apparent(obs) AS apparent_el, + atmospheric_refraction(topo_elevation(obs)) AS refraction +FROM planet_observe(5, '40.0N 105.3W'::observer, NOW()) AS obs; +``` + +### Refracted satellite passes (extended visibility windows) + +```sql +SELECT pass_aos_time(p), pass_max_elevation(p), pass_duration(p) +FROM satellites, + LATERAL predict_passes_refracted(elements, '40.0N 105.3W 1655m'::observer, + NOW(), NOW() + '3 days'::interval, 10.0) AS p +WHERE name = 'ISS'; +``` + ## Error Handling ### _safe() variants @@ -480,6 +562,7 @@ AU = 149597870.7 km (IAU 2012) Gauss k = 0.01720209895 AU^(3/2)/day Obliquity J2000 = 23.4392911° J2000 epoch = JD 2451545.0 (2000 Jan 1.5 TT) +c (light) = 173.1446327 AU/day (for light-time correction) ``` ### Critical rule diff --git a/docs/public/llms.txt b/docs/public/llms.txt index abd15e1..514b686 100644 --- a/docs/public/llms.txt +++ b/docs/public/llms.txt @@ -1,6 +1,6 @@ # pg_orrery -> Celestial mechanics types and functions for PostgreSQL. Native C extension with 82 SQL functions, 8 custom types, GiST/SP-GiST indexing. Covers satellites (SGP4/SDP4), planets (VSOP87), Moon (ELP2000-82B), 19 planetary moons, stars, comets, asteroids (MPC catalog), Jupiter radio bursts, orbit determination, and interplanetary Lambert transfers. Optional JPL DE440/441 ephemeris for sub-arcsecond accuracy. +> Celestial mechanics types and functions for PostgreSQL. Native C extension with 106 SQL functions, 9 custom types, GiST/SP-GiST indexing. Covers satellites (SGP4/SDP4), planets (VSOP87), Moon (ELP2000-82B), 19 planetary moons, stars (with proper motion), comets, asteroids (MPC catalog), Jupiter radio bursts, orbit determination, interplanetary Lambert transfers, equatorial RA/Dec coordinates, atmospheric refraction, and light-time correction. Optional JPL DE440/441 ephemeris for sub-arcsecond accuracy. - [Source code](https://git.supported.systems/warehack.ing/pg_orrery) - [Full LLM reference](https://pg-orrery.warehack.ing/llms-full.txt): All function signatures, types, body IDs, operators, and query patterns inline @@ -39,14 +39,15 @@ ## Reference -- [Types](https://pg-orrery.warehack.ing/reference/types/): 8 fixed-size types — tle (112B), eci_position (48B), geodetic (24B), topocentric (32B), observer (24B), pass_event (48B), heliocentric (24B), orbital_elements (72B), plus observer_window composite -- [Functions: Satellite](https://pg-orrery.warehack.ing/reference/functions-satellite/): 22 functions — SGP4/SDP4 propagation, coordinate transforms, pass prediction, observation -- [Functions: Solar System](https://pg-orrery.warehack.ing/reference/functions-solar-system/): VSOP87 planets, Sun, Moon observation and heliocentric positions +- [Types](https://pg-orrery.warehack.ing/reference/types/): 9 fixed-size types — tle (112B), eci_position (48B), geodetic (24B), topocentric (32B), observer (24B), pass_event (48B), heliocentric (24B), orbital_elements (72B), equatorial (24B), plus observer_window composite +- [Functions: Satellite](https://pg-orrery.warehack.ing/reference/functions-satellite/): 22 functions — SGP4/SDP4 propagation, coordinate transforms, pass prediction, observation, satellite RA/Dec (topocentric + geocentric) +- [Functions: Solar System](https://pg-orrery.warehack.ing/reference/functions-solar-system/): VSOP87 planets, Sun, Moon — observation, heliocentric positions, equatorial RA/Dec, light-time corrected _apparent() variants - [Functions: Moons](https://pg-orrery.warehack.ing/reference/functions-moons/): Galilean, Saturn, Uranus, Mars moon observation via analytical theories -- [Functions: Stars & Comets](https://pg-orrery.warehack.ing/reference/functions-stars-comets/): Star observation, Keplerian propagation, comet/asteroid observation, MPC parsing, orbital_elements functions +- [Functions: Stars & Comets](https://pg-orrery.warehack.ing/reference/functions-stars-comets/): Star observation with proper motion, Keplerian propagation, comet/asteroid observation + RA/Dec, MPC parsing, orbital_elements functions - [Functions: Radio](https://pg-orrery.warehack.ing/reference/functions-radio/): Jupiter decametric radio burst prediction — Io phase, CML, burst probability - [Functions: Transfers](https://pg-orrery.warehack.ing/reference/functions-transfers/): Lambert transfer solver for interplanetary trajectory design -- [Functions: DE Ephemeris](https://pg-orrery.warehack.ing/reference/functions-de/): Optional JPL DE440/441 variants of all observation functions +- [Functions: Refraction](https://pg-orrery.warehack.ing/reference/functions-refraction/): Bennett (1982) atmospheric refraction, P/T correction, apparent elevation, refracted pass prediction +- [Functions: DE Ephemeris](https://pg-orrery.warehack.ing/reference/functions-de/): Optional JPL DE440/441 variants of observation and equatorial functions - [Functions: Orbit Determination](https://pg-orrery.warehack.ing/reference/functions-od/): TLE fitting from ECI, topocentric, and angles-only observations - [Operators & Indexes](https://pg-orrery.warehack.ing/reference/operators-gist/): GiST (&&, <->) and SP-GiST (&?) operator classes for orbital indexing - [Body ID Reference](https://pg-orrery.warehack.ing/reference/body-ids/): Planet IDs 0–10, Galilean 0–3, Saturn 0–7, Uranus 0–4, Mars 0–1 diff --git a/docs/src/content/docs/guides/conjunction-screening.mdx b/docs/src/content/docs/guides/conjunction-screening.mdx index 267584d..3c067d2 100644 --- a/docs/src/content/docs/guides/conjunction-screening.mdx +++ b/docs/src/content/docs/guides/conjunction-screening.mdx @@ -32,9 +32,9 @@ The two operators: | Operator | Type | What it checks | |---|---|---| | `tle && tle` | boolean | Altitude band AND inclination range overlap | -| `tle <-> tle` | float8 | Minimum altitude-band separation in km | +| `tle <-> tle` | float8 | 2-D orbital distance in km (altitude + inclination) | -The `&&` operator is used for overlap queries (find all objects in the same shell). The `<->` operator is used for nearest-neighbor queries (find the N closest objects by altitude separation). +The `&&` operator is used for overlap queries (find all objects in the same shell). The `<->` operator is used for nearest-neighbor queries (find the N closest objects by orbital distance, combining altitude gap with inclination gap converted to km). ## What pg_orrery does not replace @@ -44,7 +44,7 @@ GiST-based conjunction screening is a coarse filter. It finds candidates that sh - **Not a probability of collision.** pg_orrery does not compute Pc (probability of collision). It identifies objects in overlapping orbital shells and computes distances at discrete time steps. For Pc calculation, use CARA (Conjunction Assessment Risk Analysis) methods. - **No covariance propagation.** SGP4 does not produce covariance matrices. The distance values have no uncertainty bounds. For operational conjunction assessment, use SP ephemerides with covariance (from CDMs or owner/operator data). -- **Altitude-band approximation.** The GiST key uses perigee-to-apogee altitude as a 1-D range and inclination as a second dimension. Two TLEs can share an altitude shell and never approach because their RAANs or phases are far apart. Always follow GiST filtering with full propagation. +- **Orbital envelope approximation.** The GiST key uses perigee-to-apogee altitude and inclination as a 2-D bounding box. The `<->` distance combines both dimensions. Two TLEs can still be close in this metric and never approach because their RAANs or phases are far apart. Always follow GiST filtering with full propagation. - **No maneuver planning.** pg_orrery identifies close approaches. It does not compute avoidance maneuvers (delta-v, timing, constraints). The workflow is: GiST narrows → `tle_distance()` verifies → operator/analyst decides. @@ -120,20 +120,20 @@ ORDER BY a.name, b.name; Key insight: ISS and Equatorial-LEO are at the same altitude but different inclinations. The `&&` operator returns **false** for this pair because the 2-D key requires overlap in BOTH altitude AND inclination. Two objects at the same altitude but in very different orbital planes are unlikely to conjunct. -### Altitude-band distance with `<->` +### Orbital distance with `<->` -The `<->` operator returns the minimum separation between altitude bands, in km: +The `<->` operator returns the 2-D orbital distance in km, combining altitude-band separation with inclination gap (converted to km via Earth radius): ```sql SELECT a.name AS sat_a, b.name AS sat_b, - round((a.tle <-> b.tle)::numeric, 0) AS alt_separation_km + round((a.tle <-> b.tle)::numeric, 0) AS orbital_dist_km FROM catalog a, catalog b WHERE a.norad_id < b.norad_id ORDER BY a.tle <-> b.tle; ``` -ISS and Equatorial-LEO should show ~0 km separation (same altitude shell). ISS and GPS should show ~19,800 km (vastly different orbits). +ISS and Equatorial-LEO show ~5192 km (0 km altitude gap, but 47° inclination difference × 6378 km/rad). ISS and Hubble show ~2582 km (115 km altitude gap + 23° inclination difference). ISS and GPS show ~19,456 km (altitude gap dominates). ### GiST index scan: find overlapping orbits @@ -152,22 +152,22 @@ RESET enable_seqscan; This should return only ISS itself (and not Equatorial-LEO, which has a different inclination). The GiST index scan avoids checking every object in the catalog. -### K-nearest-neighbor by altitude +### K-nearest-neighbor by orbital distance -Find the 3 closest objects to the ISS by altitude band separation, ordered by distance: +Find the 3 closest objects to the ISS by 2-D orbital distance, ordered by distance: ```sql -- Scalar subquery probe enables GiST index-ordered scan SELECT name, round((tle <-> (SELECT tle FROM catalog WHERE norad_id = 25544 LIMIT 1))::numeric, 0) - AS alt_dist_km + AS orbital_dist_km FROM catalog WHERE norad_id != 25544 ORDER BY tle <-> (SELECT tle FROM catalog WHERE norad_id = 25544 LIMIT 1) LIMIT 3; ``` -This uses the GiST distance operator for efficient ordering. PostgreSQL's KNN-GiST infrastructure traverses the tree by increasing distance without computing all distances upfront. On a 66,440-object catalog, this completes in 2.1 ms for 10 neighbors. +This uses the GiST distance operator for efficient ordering. The 2-D metric means satellites at the same altitude but wildly different inclinations no longer tie at distance 0 --- Hubble (inc 28°, 115 km altitude gap) ranks ahead of an equatorial LEO object (inc 5°, 0 km altitude gap but 47° inclination difference). PostgreSQL's KNN-GiST infrastructure traverses the tree by increasing distance without computing all distances upfront. On a 66,440-object catalog, this completes in 2.1 ms for 10 neighbors.