Operators & GiST Index
pg_orbit defines two operators on the tle type and a GiST operator class that enables indexed conjunction screening over large satellite catalogs. The operators work on the orbital altitude band and inclination range extracted from TLE elements, providing a fast necessary-condition filter for proximity analysis.
Operators
Section titled “Operators”&& (Overlap)
Section titled “&& (Overlap)”Tests whether two TLEs have overlapping orbital envelopes. The envelopes are defined by the altitude band (perigee to apogee) and inclination range. Overlap is a necessary but not sufficient condition for a conjunction: two satellites whose altitude bands and inclination ranges do not overlap can never come close to each other.
Signature
Section titled “Signature”tle && tle → booleanDescription
Section titled “Description”Returns true if both of the following conditions hold:
- The altitude bands overlap (one satellite’s perigee is below the other’s apogee, and vice versa)
- The inclination ranges are compatible (the orbits could geometrically intersect)
Returns false if the orbits are guaranteed to never intersect based on these geometric bounds.
Example
Section titled “Example”-- Find all satellites whose orbits could potentially intersect with the ISSWITH iss AS ( SELECT '1 25544U 98067A 24001.50000000 .00016717 00000-0 10270-3 0 90252 25544 51.6400 208.9163 0006703 30.1694 61.7520 15.50100486 00001'::tle AS tle)SELECT norad_id, nameFROM satellite_catalog, issWHERE satellite_catalog.tle && iss.tle;<-> (Distance)
Section titled “<-> (Distance)”Computes the minimum separation between the altitude bands of two TLEs, in kilometers. If the altitude bands overlap, returns 0.
Signature
Section titled “Signature”tle <-> tle → float8Description
Section titled “Description”This is an altitude-only metric. It computes:
max(0, perigee_a - apogee_b)andmax(0, perigee_b - apogee_a)- Returns the minimum of these two values
The result is the minimum possible radial separation. A result of 0 means the altitude bands overlap (but the satellites may still be far apart in along-track or cross-track distance).
Example
Section titled “Example”-- Altitude band separation between ISS and a GEO satelliteWITH iss AS ( SELECT '1 25544U 98067A 24001.50000000 .00016717 00000-0 10270-3 0 90252 25544 51.6400 208.9163 0006703 30.1694 61.7520 15.50100486 00001'::tle AS tle),geo AS ( SELECT '1 28884U 05041A 24001.50000000 -.00000089 00000-0 00000-0 0 99972 28884 0.0153 93.0424 0001699 138.1498 336.5718 1.00271128 67481'::tle AS tle)SELECT round((iss.tle <-> geo.tle)::numeric, 1) AS separation_kmFROM iss, geo;-- Order catalog by altitude proximity to a target satelliteWITH target AS ( SELECT tle FROM satellite_catalog WHERE norad_id = 25544)SELECT norad_id, name, round((satellite_catalog.tle <-> target.tle)::numeric, 1) AS alt_sep_kmFROM satellite_catalog, targetORDER BY satellite_catalog.tle <-> target.tleLIMIT 20;GiST Operator Class: tle_ops
Section titled “GiST Operator Class: tle_ops”The tle_ops operator class enables GiST indexing on tle columns. With this index in place, the && (overlap) and <-> (distance) operators use index scans instead of sequential scans, making conjunction screening over large catalogs practical.
Creating the Index
Section titled “Creating the Index”CREATE INDEX idx_tle_gist ON satellite_catalog USING gist (tle tle_ops);What Gets Indexed
Section titled “What Gets Indexed”The GiST index stores a bounding representation of each TLE’s orbital envelope:
- Altitude band: perigee altitude to apogee altitude (km, above WGS-72)
- Inclination range: orbital inclination (degrees)
These are extracted from the TLE’s mean motion and eccentricity at index creation time. The index does not store time-varying quantities; it represents the geometric envelope of the orbit as defined by the current osculating elements.
Index-Accelerated Queries
Section titled “Index-Accelerated Queries”-- Find all catalog objects that could intersect with the ISS orbit-- Uses the GiST index to avoid a full catalog scanWITH iss AS ( SELECT tle FROM satellite_catalog WHERE norad_id = 25544)SELECT c.norad_id, c.nameFROM satellite_catalog c, issWHERE c.tle && iss.tle AND c.norad_id != 25544;-- Find the 10 satellites with the closest altitude bands to the ISS-- The <-> operator supports GiST ordering (ORDER BY ... <-> ...)WITH iss AS ( SELECT tle FROM satellite_catalog WHERE norad_id = 25544)SELECT c.norad_id, c.name, round((c.tle <-> iss.tle)::numeric, 1) AS alt_sep_kmFROM satellite_catalog c, issWHERE c.norad_id != 25544ORDER BY c.tle <-> iss.tleLIMIT 10;-- Full conjunction screening pipeline:-- Stage 1: GiST index filters by orbital envelope overlap-- Stage 2: Precise distance computation on surviving pairsWITH iss AS ( SELECT tle FROM satellite_catalog WHERE norad_id = 25544),candidates AS ( SELECT c.norad_id, c.name, c.tle FROM satellite_catalog c, iss WHERE c.tle && iss.tle AND c.norad_id != 25544)SELECT norad_id, name, round(tle_distance(candidates.tle, iss.tle, now())::numeric, 1) AS dist_kmFROM candidates, issWHERE tle_distance(candidates.tle, iss.tle, now()) < 100ORDER BY dist_km;Performance
Section titled “Performance”Without the GiST index, the && operator requires a sequential scan of the entire catalog (O(n) per query). With the index, overlap queries run in O(log n) time. For a catalog of 12,000 active TLEs, this reduces conjunction screening from seconds to milliseconds.
Index Maintenance
Section titled “Index Maintenance”The GiST index is maintained automatically by PostgreSQL on INSERT, UPDATE, and DELETE. When TLEs are refreshed (e.g., daily catalog updates), the index is updated in place. No manual REINDEX is needed under normal operation.
If you perform a bulk catalog replacement (truncate + reload), run REINDEX after loading:
TRUNCATE satellite_catalog;COPY satellite_catalog FROM '/path/to/catalog.csv' WITH (FORMAT csv);REINDEX INDEX idx_tle_gist;