-- 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; -- ============================================================ -- Cleanup -- ============================================================ DROP TABLE test_spgist;