pg_orrery/test/expected/spgist_tle.out
Ryan Malloy 747b7ae60a Fix L1 inclination pruning for HEO orbits, add 66k benchmark
Bug: inner_consistent used sma_low for footprint calculation, but
ground footprint grows with altitude. High-SMA bins (GTO, HEO)
need sma_high to compute the maximum footprint — using sma_low
caused 453 false negatives at high-latitude observers (Tromsoe).

Fix: use sma_high (not sma_low) in L1 inclination pruning.

Added regression test: GTO-debris (inc 5 deg, e=0.73) at Tromsoe
must return identical results from seqscan and index scan.

Benchmark on 65,886-object catalog (full Space-Track including
decayed): 80-92% pruning, zero false negatives across 7 query
patterns. SP-GiST beats seqscan for high-latitude observers.
2026-02-17 23:05:49 -07:00

355 lines
11 KiB
Plaintext

-- Test SP-GiST orbital trie index and &? visibility cone operator
SET client_min_messages = warning;
CREATE EXTENSION IF NOT EXISTS pg_orrery;
RESET client_min_messages;
-- ============================================================
-- Test table with mixed orbital regimes
-- ============================================================
CREATE TABLE test_spgist (
id serial,
name text,
tle tle
);
-- ISS (LEO, ~400km, 51.64 deg)
INSERT INTO test_spgist (name, tle) VALUES ('ISS',
'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');
-- Hubble (LEO, ~540km, 28.47 deg)
INSERT INTO test_spgist (name, tle) VALUES ('Hubble',
'1 20580U 90037B 24001.50000000 .00000790 00000+0 39573-4 0 9992
2 20580 28.4705 61.4398 0002797 317.3115 42.7577 15.09395228 00008');
-- GPS IIR-M (MEO, ~20200km, 55.44 deg)
INSERT INTO test_spgist (name, tle) VALUES ('GPS-IIR',
'1 28874U 05038A 24001.50000000 .00000012 00000+0 00000+0 0 9993
2 28874 55.4408 300.3467 0117034 51.6543 309.5420 2.00557079 00006');
-- Equatorial-LEO (same altitude as ISS, 5 deg inclination)
INSERT INTO test_spgist (name, tle) VALUES ('Equatorial-LEO',
'1 99901U 24999A 24001.50000000 .00016717 00000-0 10270-3 0 9990
2 99901 5.0000 208.9163 0006703 30.1694 61.7520 15.50100486 00001');
-- SSO-800 (Sun-synchronous, ~800km, 98.7 deg)
INSERT INTO test_spgist (name, tle) VALUES ('SSO-800',
'1 99902U 24999B 24001.50000000 .00000100 00000+0 50000-4 0 9991
2 99902 98.7000 120.0000 0001000 90.0000 270.0000 14.19553000 00001');
-- GEO-SAT (Geostationary, ~35786km, 0.04 deg)
INSERT INTO test_spgist (name, tle) VALUES ('GEO-SAT',
'1 99903U 24999C 24001.50000000 .00000000 00000+0 00000+0 0 9992
2 99903 0.0400 270.0000 0003000 0.0000 180.0000 1.00273791 00001');
-- ============================================================
-- Test 1: Operator standalone — ISS from Eagle Idaho (2h window)
-- Eagle Idaho: 43.6977N 116.3535W, 760m elevation
-- ISS passes altitude and inclination checks, but RAAN filter
-- rejects it — the orbital plane isn't overhead during this
-- specific 2-hour window (correct physics, see Test 5 for 24h).
-- ============================================================
SELECT name,
tle &? ROW(
observer('43.6977N 116.3535W 760m'),
'2024-01-01 00:00:00+00'::timestamptz,
'2024-01-01 02:00:00+00'::timestamptz,
10.0
)::observer_window AS visible
FROM test_spgist
WHERE name = 'ISS';
name | visible
------+---------
ISS | f
(1 row)
-- ============================================================
-- Test 2: Equatorial-LEO NOT visible from Eagle Idaho
-- 5 deg inc + ~12 deg footprint = 17 deg < 43.7 deg latitude
-- ============================================================
SELECT name,
tle &? ROW(
observer('43.6977N 116.3535W 760m'),
'2024-01-01 00:00:00+00'::timestamptz,
'2024-01-01 02:00:00+00'::timestamptz,
10.0
)::observer_window AS visible
FROM test_spgist
WHERE name = 'Equatorial-LEO';
name | visible
----------------+---------
Equatorial-LEO | f
(1 row)
-- ============================================================
-- Test 3: Create SP-GiST index, verify index scan with positive
-- results. Equatorial observer at 0E — SSO-800 RAAN (120 deg)
-- aligns with LST near 0E at this epoch, so it passes.
-- ============================================================
CREATE INDEX test_spgist_idx ON test_spgist USING spgist (tle tle_spgist_ops);
SET enable_seqscan = off;
SELECT name
FROM test_spgist
WHERE tle &? ROW(
observer('0.0N 0.0E 0m'),
'2024-01-01 00:00:00+00'::timestamptz,
'2024-01-01 02:00:00+00'::timestamptz,
10.0
)::observer_window
ORDER BY name;
name
---------
SSO-800
(1 row)
RESET enable_seqscan;
-- ============================================================
-- Test 4: Seqscan vs index scan consistency — same query must
-- return identical results regardless of scan method.
-- ============================================================
SET enable_indexscan = off;
SET enable_bitmapscan = off;
SELECT name
FROM test_spgist
WHERE tle &? ROW(
observer('0.0N 0.0E 0m'),
'2024-01-01 00:00:00+00'::timestamptz,
'2024-01-01 02:00:00+00'::timestamptz,
10.0
)::observer_window
ORDER BY name;
name
---------
SSO-800
(1 row)
RESET enable_indexscan;
RESET enable_bitmapscan;
-- ============================================================
-- Test 5: 24-hour window — RAAN filter bypassed (full Earth
-- rotation). Only ISS and SSO-800 pass inclination from Eagle
-- Idaho (43.7 deg). Hubble (28.5+14.8=43.3 deg) barely fails.
-- GPS-IIR and GEO-SAT filtered by altitude.
-- ============================================================
SELECT name
FROM test_spgist
WHERE tle &? ROW(
observer('43.6977N 116.3535W 760m'),
'2024-01-01 00:00:00+00'::timestamptz,
'2024-01-02 00:00:00+00'::timestamptz,
10.0
)::observer_window
ORDER BY name;
name
---------
ISS
SSO-800
(2 rows)
-- ============================================================
-- Test 6: High min_el (45 deg) changes footprint — wider
-- footprint lets more inclinations through. Same 24h window.
-- ============================================================
SELECT name
FROM test_spgist
WHERE tle &? ROW(
observer('43.6977N 116.3535W 760m'),
'2024-01-01 00:00:00+00'::timestamptz,
'2024-01-02 00:00:00+00'::timestamptz,
45.0
)::observer_window
ORDER BY name;
name
---------
ISS
SSO-800
(2 rows)
-- ============================================================
-- Test 7: GiST coexistence — both index types on same table
-- ============================================================
CREATE INDEX test_gist_idx ON test_spgist USING gist (tle);
-- GiST overlap query still works
SELECT a.name AS sat_a, b.name AS sat_b, a.tle && b.tle AS overlaps
FROM test_spgist a, test_spgist b
WHERE a.name = 'ISS' AND b.name = 'Hubble';
sat_a | sat_b | overlaps
-------+--------+----------
ISS | Hubble | f
(1 row)
-- SP-GiST query still works alongside GiST
SET enable_seqscan = off;
SELECT name
FROM test_spgist
WHERE tle &? ROW(
observer('43.6977N 116.3535W 760m'),
'2024-01-01 00:00:00+00'::timestamptz,
'2024-01-02 00:00:00+00'::timestamptz,
10.0
)::observer_window
ORDER BY name;
name
---------
ISS
SSO-800
(2 rows)
RESET enable_seqscan;
-- ============================================================
-- Test 8: NULL TLE handling — NULLs should be excluded
-- ============================================================
INSERT INTO test_spgist (name, tle) VALUES ('NULL-SAT', NULL);
SELECT name
FROM test_spgist
WHERE tle &? ROW(
observer('43.6977N 116.3535W 760m'),
'2024-01-01 00:00:00+00'::timestamptz,
'2024-01-02 00:00:00+00'::timestamptz,
10.0
)::observer_window
ORDER BY name;
name
---------
ISS
SSO-800
(2 rows)
-- ============================================================
-- Test 9: Degenerate TLE (mean_motion = 0) — rejected by filter
-- ============================================================
INSERT INTO test_spgist (name, tle) VALUES ('DECAYED',
'1 99904U 24999D 24001.50000000 .00000000 00000+0 00000+0 0 9993
2 99904 0.0000 0.0000 0000000 0.0000 0.0000 0.00000000 00001');
SELECT name,
tle &? ROW(
observer('0.0N 0.0E 0m'),
'2024-01-01 00:00:00+00'::timestamptz,
'2024-01-02 00:00:00+00'::timestamptz,
10.0
)::observer_window AS visible
FROM test_spgist
WHERE name = 'DECAYED';
name | visible
---------+---------
DECAYED | f
(1 row)
-- ============================================================
-- Test 10: Polar observer (90N) — only ISS and SSO-800 reach
-- the pole. ISS (51.6 + footprint) < 90, so only SSO-800
-- (retrograde, 98.7 deg inc > 90 deg) passes. 24h window.
-- ============================================================
SELECT name
FROM test_spgist
WHERE tle &? ROW(
observer('90.0N 0.0E 0m'),
'2024-01-01 00:00:00+00'::timestamptz,
'2024-01-02 00:00:00+00'::timestamptz,
10.0
)::observer_window
ORDER BY name;
name
---------
SSO-800
(1 row)
-- ============================================================
-- Test 11: Zero-duration window — sees only what is directly
-- overhead at the instant. RAAN window = footprint only.
-- ============================================================
SELECT name,
tle &? ROW(
observer('0.0N 0.0E 0m'),
'2024-01-01 00:00:00+00'::timestamptz,
'2024-01-01 00:00:00+00'::timestamptz,
10.0
)::observer_window AS visible
FROM test_spgist
WHERE name = 'ISS';
name | visible
------+---------
ISS | f
(1 row)
-- ============================================================
-- Test 12: Index-vs-seqscan consistency on 24h Eagle Idaho
-- (the primary correctness test, now after all inserts)
-- ============================================================
SET enable_seqscan = off;
SELECT name
FROM test_spgist
WHERE tle &? ROW(
observer('43.6977N 116.3535W 760m'),
'2024-01-01 00:00:00+00'::timestamptz,
'2024-01-02 00:00:00+00'::timestamptz,
10.0
)::observer_window
ORDER BY name;
name
---------
ISS
SSO-800
(2 rows)
RESET enable_seqscan;
SET enable_indexscan = off;
SET enable_bitmapscan = off;
SELECT name
FROM test_spgist
WHERE tle &? ROW(
observer('43.6977N 116.3535W 760m'),
'2024-01-01 00:00:00+00'::timestamptz,
'2024-01-02 00:00:00+00'::timestamptz,
10.0
)::observer_window
ORDER BY name;
name
---------
ISS
SSO-800
(2 rows)
RESET enable_indexscan;
RESET enable_bitmapscan;
-- ============================================================
-- Test 13: HEO at high latitude — GTO-class orbit (low inc,
-- high SMA, high eccentricity) from Tromsø (69.6°N).
-- The large SMA gives a huge footprint that compensates for the
-- low inclination. Must pass the seqscan operator check.
-- Regression test for the L1 pruning bug (sma_low vs sma_high).
-- ============================================================
-- GTO debris: inc 5 deg, perigee ~250 km, apogee ~35786 km
INSERT INTO test_spgist (name, tle) VALUES ('GTO-DEBRIS',
'1 99905U 24999E 24001.50000000 .00000100 00000+0 10000-3 0 9994
2 99905 5.0000 210.0000 7300000 30.0000 61.0000 2.25600000 00001');
-- Seqscan: GTO-DEBRIS from Tromsø — must be visible
-- inc 5 deg + footprint(SMA ~25000) ~65 deg = 70 > 69.6
SELECT name,
tle &? ROW(
observer('69.6N 19.0E 0m'),
'2024-01-01 00:00:00+00'::timestamptz,
'2024-01-02 00:00:00+00'::timestamptz,
10.0
)::observer_window AS visible
FROM test_spgist
WHERE name = 'GTO-DEBRIS';
name | visible
------------+---------
GTO-DEBRIS | t
(1 row)
-- Index scan: same query, must return the same result
SET enable_seqscan = off;
SELECT name,
tle &? ROW(
observer('69.6N 19.0E 0m'),
'2024-01-01 00:00:00+00'::timestamptz,
'2024-01-02 00:00:00+00'::timestamptz,
10.0
)::observer_window AS visible
FROM test_spgist
WHERE name = 'GTO-DEBRIS';
name | visible
------------+---------
GTO-DEBRIS | t
(1 row)
RESET enable_seqscan;
-- ============================================================
-- Cleanup
-- ============================================================
DROP TABLE test_spgist;