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_orbit.


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');