Skip to content

Functions: Satellite

Functions for propagating TLEs, converting coordinate frames, computing ground tracks, and predicting satellite passes. These form the core satellite tracking pipeline in pg_orrery.


Propagates a TLE to a given time using the SGP4 (near-earth) or SDP4 (deep-space) algorithm. The algorithm is selected automatically based on orbital period: elements with a period >= 225 minutes use SDP4.

sgp4_propagate(tle tle, t timestamptz) → eci_position
ParameterTypeDescription
tletleTwo-Line Element set to propagate
ttimestamptzTarget epoch for propagation

An eci_position in the TEME reference frame. Position in km, velocity in km/s.

Raises an exception if SGP4/SDP4 returns a fatal error code (e.g., satellite decay, eccentricity out of range, mean motion near zero). Use sgp4_propagate_safe if you need NULL-on-error behavior.

WITH iss AS (
SELECT '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'::tle AS tle
)
SELECT eci_x(pos) AS x_km,
eci_y(pos) AS y_km,
eci_z(pos) AS z_km,
eci_speed(pos) AS speed_kms
FROM iss, sgp4_propagate(tle, '2024-01-02 12:00:00+00') AS pos;

Identical to sgp4_propagate, but returns NULL instead of raising an exception on propagation errors. This is the batch-safe variant for processing large TLE catalogs where some elements may be stale or invalid.

sgp4_propagate_safe(tle tle, t timestamptz) → eci_position
ParameterTypeDescription
tletleTwo-Line Element set to propagate
ttimestamptzTarget epoch for propagation

An eci_position, or NULL if propagation fails.

-- Propagate an entire catalog, skipping failed elements
SELECT norad_id,
eci_x(pos) AS x_km,
eci_y(pos) AS y_km,
eci_z(pos) AS z_km
FROM satellite_catalog,
sgp4_propagate_safe(tle, now()) AS pos
WHERE pos IS NOT NULL;

Generates a time series of TEME ECI positions for a single TLE over a time range. Returns one row per time step. This is significantly faster than calling sgp4_propagate inside a generate_series because the SGP4 initializer runs once.

sgp4_propagate_series(
tle tle,
start_time timestamptz,
end_time timestamptz,
step interval
) → TABLE(t timestamptz, x float8, y float8, z float8, vx float8, vy float8, vz float8)
ParameterTypeDescription
tletleTwo-Line Element set
start_timetimestamptzStart of the time range
end_timetimestamptzEnd of the time range (inclusive if aligned to step)
stepintervalTime between samples

A set of rows with timestamp and TEME position/velocity components. Position in km, velocity in km/s.

WITH iss AS (
SELECT '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'::tle AS tle
)
SELECT t, x, y, z, vx, vy, vz
FROM iss,
sgp4_propagate_series(tle,
'2024-01-02 00:00:00+00',
'2024-01-02 01:00:00+00',
interval '1 minute');

Computes the Euclidean distance between two satellites at a given time. Both TLEs are propagated to the target time and the 3D distance between their TEME positions is returned.

tle_distance(a tle, b tle, t timestamptz) → float8
ParameterTypeDescription
atleFirst satellite
btleSecond satellite
ttimestamptzEvaluation time

Distance in kilometers. Raises an exception if either TLE fails to propagate.

-- Distance between two satellites at a specific time
SELECT tle_distance(sat_a.tle, sat_b.tle, '2024-06-15 12:00:00+00') AS dist_km
FROM satellite_catalog sat_a, satellite_catalog sat_b
WHERE sat_a.norad_id = 25544 -- ISS
AND sat_b.norad_id = 48274; -- CSS (Tianhe)

Converts a TEME ECI position to WGS-84 geodetic coordinates. The timestamp is required to compute the Earth’s rotation angle (Greenwich Apparent Sidereal Time).

eci_to_geodetic(pos eci_position, t timestamptz) → geodetic
ParameterTypeDescription
poseci_positionTEME ECI position
ttimestamptzTime of the position (for sidereal time computation)

A geodetic with WGS-84 latitude, longitude, and altitude.

WITH iss AS (
SELECT '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'::tle AS tle
)
SELECT geo_lat(g) AS lat,
geo_lon(g) AS lon,
geo_alt(g) AS alt_km
FROM iss,
eci_to_geodetic(sgp4_propagate(tle, now()), now()) AS g;

Converts a TEME ECI position to topocentric (observer-relative) coordinates. Computes azimuth, elevation, slant range, and range rate.

eci_to_topocentric(pos eci_position, obs observer, t timestamptz) → topocentric
ParameterTypeDescription
poseci_positionTEME ECI position and velocity
obsobserverObserver location
ttimestamptzTime of the position (for sidereal time computation)

A topocentric with azimuth, elevation, range, and range rate.

WITH iss AS (
SELECT '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'::tle AS tle
)
SELECT topo_azimuth(tc) AS az,
topo_elevation(tc) AS el,
topo_range(tc) AS range_km,
topo_range_rate(tc) AS range_rate_kms
FROM iss,
eci_to_topocentric(
sgp4_propagate(tle, now()),
'40.0N 105.3W 1655m'::observer,
now()
) AS tc;

Returns the nadir (directly below the satellite) point on the WGS-84 ellipsoid for a given TLE at a given time. This is a convenience function equivalent to propagating and then converting to geodetic, but with altitude set to the satellite altitude.

subsatellite_point(tle tle, t timestamptz) → geodetic
ParameterTypeDescription
tletleSatellite TLE
ttimestamptzEvaluation time

A geodetic with the latitude, longitude, and altitude of the satellite above the WGS-84 ellipsoid.

WITH iss AS (
SELECT '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'::tle AS tle
)
SELECT geo_lat(sp) AS nadir_lat,
geo_lon(sp) AS nadir_lon,
geo_alt(sp) AS altitude_km
FROM iss, subsatellite_point(tle, now()) AS sp;

Generates a time series of subsatellite points (nadir ground track) for a satellite over a time range. Each row contains the timestamp, latitude, longitude, and altitude.

ground_track(
tle tle,
start_time timestamptz,
end_time timestamptz,
step interval
) → TABLE(t timestamptz, lat float8, lon float8, alt float8)
ParameterTypeDescription
tletleSatellite TLE
start_timetimestamptzStart of the time range
end_timetimestamptzEnd of the time range
stepintervalTime between samples

A set of rows with timestamp, latitude (degrees), longitude (degrees), and altitude (km).

-- ISS ground track for one orbit (~92 minutes) at 30-second resolution
WITH iss AS (
SELECT '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'::tle AS tle
)
SELECT t, lat, lon, alt
FROM iss, ground_track(tle, now(), now() + interval '92 minutes', interval '30 seconds');

Propagates a TLE and computes topocentric look angles in a single call. Equivalent to eci_to_topocentric(sgp4_propagate(tle, t), obs, t), but avoids the intermediate allocation.

observe(tle tle, obs observer, t timestamptz) → topocentric
ParameterTypeDescription
tletleSatellite TLE
obsobserverObserver location
ttimestamptzObservation time

A topocentric with azimuth, elevation, range, and range rate.

WITH iss AS (
SELECT '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'::tle AS tle
)
SELECT topo_azimuth(o) AS az,
topo_elevation(o) AS el,
topo_range(o) AS range_km
FROM iss, observe(tle, '40.0N 105.3W 1655m'::observer, now()) AS o;

Identical to observe, but returns NULL instead of raising an exception on propagation errors. Use this when processing large TLE catalogs in batch.

observe_safe(tle tle, obs observer, t timestamptz) → topocentric
ParameterTypeDescription
tletleSatellite TLE
obsobserverObserver location
ttimestamptzObservation time

A topocentric, or NULL if propagation fails.

-- Find all satellites above the horizon right now, skipping stale TLEs
SELECT norad_id,
topo_azimuth(o) AS az,
topo_elevation(o) AS el
FROM satellite_catalog,
observe_safe(tle, '40.0N 105.3W 1655m'::observer, now()) AS o
WHERE o IS NOT NULL
AND topo_elevation(o) > 0
ORDER BY topo_elevation(o) DESC;

Finds the next satellite pass over an observer location. Searches forward from the given start time up to 7 days.

next_pass(tle tle, obs observer, start timestamptz) → pass_event
ParameterTypeDescription
tletleSatellite TLE
obsobserverObserver location
starttimestamptzTime to begin searching from

A pass_event with AOS, maximum elevation, LOS, azimuths, and duration. Returns NULL if no pass is found within 7 days (possible for equatorial observers looking for high-inclination satellites, or vice versa).

WITH iss AS (
SELECT '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'::tle AS tle
)
SELECT pass_aos_time(p) AS rise,
pass_max_elevation(p) AS max_el,
pass_los_time(p) AS set,
pass_duration(p) AS duration
FROM iss, next_pass(tle, '40.0N 105.3W 1655m'::observer, now()) AS p;

Finds all satellite passes over an observer within a time window, optionally filtered by minimum elevation. Returns a set of pass_event records.

predict_passes(
tle tle,
obs observer,
start_time timestamptz,
end_time timestamptz,
min_el float8 DEFAULT 0.0
) → SETOF pass_event
ParameterTypeDefaultDescription
tletleSatellite TLE
obsobserverObserver location
start_timetimestamptzStart of the search window
end_timetimestamptzEnd of the search window
min_elfloat80.0Minimum peak elevation in degrees. Passes whose maximum elevation is below this threshold are excluded.

A set of pass_event records, ordered by AOS time.

-- All ISS passes above 20 degrees in the next 3 days
WITH iss AS (
SELECT '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'::tle AS tle
)
SELECT pass_aos_time(p) AS rise,
pass_max_elevation(p) AS max_el,
pass_aos_azimuth(p) AS rise_az,
pass_los_azimuth(p) AS set_az,
pass_duration(p) AS dur
FROM iss,
predict_passes(tle, '40.0N 105.3W 1655m'::observer,
now(), now() + interval '3 days', 20.0) AS p;

Returns true if at least one satellite pass occurs over the observer during the given time window.

pass_visible(tle tle, obs observer, start_time timestamptz, end_time timestamptz) → boolean
ParameterTypeDescription
tletleSatellite TLE
obsobserverObserver location
start_timetimestamptzStart of the search window
end_timetimestamptzEnd of the search window

true if any pass (elevation > 0) occurs in the window; false otherwise. This is faster than predict_passes when you only need a yes/no answer because it stops searching after the first pass is found.

-- Which satellites from the catalog pass over Boulder tonight?
SELECT norad_id, name
FROM satellite_catalog
WHERE pass_visible(tle, '40.0N 105.3W 1655m'::observer,
'2024-06-15 02:00:00+00', '2024-06-15 10:00:00+00');

Converts a TEME ECI position to topocentric apparent equatorial coordinates (RA/Dec) for a given observer. The observer’s position is subtracted from the satellite’s ECI vector to produce parallax-corrected coordinates. For LEO satellites, observer parallax is approximately 1 degree.

eci_to_equatorial(pos eci_position, obs observer, t timestamptz) → equatorial
ParameterTypeDescription
poseci_positionTEME ECI position and velocity
obsobserverObserver location on Earth
ttimestamptzTime of the position (for sidereal time computation)

An equatorial with RA (hours), Dec (degrees), and distance (km) from the observer’s perspective.

WITH iss AS (
SELECT '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'::tle AS tle
)
SELECT round(eq_ra(e)::numeric, 4) AS ra_hours,
round(eq_dec(e)::numeric, 4) AS dec_deg,
round(eq_distance(e)::numeric, 1) AS dist_km
FROM iss,
eci_to_equatorial(
sgp4_propagate(tle, now()),
'40.0N 105.3W 1655m'::observer,
now()
) AS e;

Converts a TEME ECI position to geocentric apparent equatorial coordinates (RA/Dec). This is the direction of the position vector as seen from Earth’s center, independent of any observer location.

eci_to_equatorial_geo(pos eci_position, t timestamptz) → equatorial
ParameterTypeDescription
poseci_positionTEME ECI position
ttimestamptzTime of the position

An equatorial with geocentric RA (hours), Dec (degrees), and distance (km) from Earth’s center.

-- Geocentric RA/Dec of the ISS
WITH iss AS (
SELECT '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'::tle AS tle
)
SELECT round(eq_ra(e)::numeric, 4) AS ra_hours,
round(eq_dec(e)::numeric, 4) AS dec_deg
FROM iss,
eci_to_equatorial_geo(sgp4_propagate(tle, now()), now()) AS e;

Predicts satellite passes using a refracted horizon threshold (-0.569 degrees geometric) instead of the geometric horizon. Atmospheric refraction makes satellites visible approximately 35 seconds earlier at AOS and later at LOS.

predict_passes_refracted(
tle tle,
obs observer,
start_time timestamptz,
end_time timestamptz,
min_el float8 DEFAULT 0.0
) → SETOF pass_event
ParameterTypeDefaultDescription
tletleSatellite TLE
obsobserverObserver location
start_timetimestamptzStart of the search window
end_timetimestamptzEnd of the search window
min_elfloat80.0Minimum peak elevation in degrees

A set of pass_event records with refraction-extended visibility windows.