pg_orrery/test/sql/convenience.sql
Ryan Malloy 5552bf3280 Add convenience functions for Craft integration
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.
2026-02-15 17:44:41 -07:00

76 lines
2.8 KiB
SQL

-- convenience functions requested by Craft (Astrolock) integration
CREATE EXTENSION IF NOT EXISTS pg_orbit;
-- tle_from_lines: two-argument constructor
SELECT tle_norad_id(tle_from_lines(
'1 25544U 98067A 24001.50000000 .00016717 00000-0 10270-3 0 9002',
'2 25544 51.6400 208.5000 0007417 68.5000 291.5000 15.49560000100001'
)) AS iss_norad_id;
-- observer_from_geodetic: numeric constructor
SELECT observer_from_geodetic(40.0, -105.3, 1655);
-- observer_from_geodetic: default altitude = 0
SELECT observer_from_geodetic(40.0, -105.3);
-- sgp4_propagate_safe: normal propagation returns result
SELECT (sgp4_propagate_safe(
'1 25544U 98067A 24001.50000000 .00016717 00000-0 10270-3 0 9002
2 25544 51.6400 208.5000 0007417 68.5000 291.5000 15.49560000100001'::tle,
'2024-01-01 12:00:00+00'::timestamptz
) IS NOT NULL) AS safe_returns_result;
-- sgp4_propagate_safe: diverged orbit returns NULL
SELECT sgp4_propagate_safe(
'1 25544U 98067A 24001.50000000 .00016717 00000-0 10270-3 0 9002
2 25544 51.6400 208.5000 0007417 68.5000 291.5000 15.49560000100001'::tle,
'2124-01-01 12:00:00+00'::timestamptz
) IS NULL AS safe_null_on_diverge;
-- observe: single-call propagate + topocentric
SELECT observe(
'1 25544U 98067A 24001.50000000 .00016717 00000-0 10270-3 0 9002
2 25544 51.6400 208.5000 0007417 68.5000 291.5000 15.49560000100001'::tle,
observer_from_geodetic(40.0, -105.3, 1655),
'2024-01-01 12:00:00+00'::timestamptz
);
-- observe_safe: returns NULL on diverged orbit
SELECT observe_safe(
'1 25544U 98067A 24001.50000000 .00016717 00000-0 10270-3 0 9002
2 25544 51.6400 208.5000 0007417 68.5000 291.5000 15.49560000100001'::tle,
'40.0N 105.3W 1655m'::observer,
'2124-01-01 12:00:00+00'::timestamptz
) IS NULL AS observe_safe_null;
-- observe matches manual eci_to_topocentric pipeline
-- (verify observe() produces same result as the two-step approach)
WITH propagated AS (
SELECT sgp4_propagate(
'1 25544U 98067A 24001.50000000 .00016717 00000-0 10270-3 0 9002
2 25544 51.6400 208.5000 0007417 68.5000 291.5000 15.49560000100001'::tle,
'2024-01-01 12:00:00+00'::timestamptz
) AS eci
),
manual AS (
SELECT eci_to_topocentric(
eci,
'40.0N 105.3W 1655m'::observer,
'2024-01-01 12:00:00+00'::timestamptz
) AS topo
FROM propagated
),
single_call AS (
SELECT observe(
'1 25544U 98067A 24001.50000000 .00016717 00000-0 10270-3 0 9002
2 25544 51.6400 208.5000 0007417 68.5000 291.5000 15.49560000100001'::tle,
'40.0N 105.3W 1655m'::observer,
'2024-01-01 12:00:00+00'::timestamptz
) AS topo
)
SELECT
topo_azimuth(m.topo) = topo_azimuth(s.topo) AS az_match,
topo_elevation(m.topo) = topo_elevation(s.topo) AS el_match,
topo_range(m.topo) = topo_range(s.topo) AS range_match
FROM manual m, single_call s;