Observation Pipeline
When a user calls planet_observe(5, '40.0N 105.3W 1655m'::observer, now()) to find Jupiter’s position in the sky, seven coordinate transformations execute in sequence. Each stage consumes the output of the previous stage and produces input for the next. Understanding this pipeline is a prerequisite for modifying any part of it.
The full pipeline
Section titled “The full pipeline”flowchart TD
A["VSOP87: Target heliocentric<br/>ecliptic J2000 (AU)"] --> C["Geocentric ecliptic<br/>target - Earth"]
B["VSOP87: Earth heliocentric<br/>ecliptic J2000 (AU)"] --> C
C --> D["Ecliptic → Equatorial J2000<br/>obliquity rotation"]
D --> E["RA/Dec J2000<br/>Cartesian → spherical"]
E --> F["Precession J2000 → date<br/>IAU 1976"]
F --> G["Sidereal time → hour angle<br/>GMST (Vallado Eq. 3-47)"]
G --> H["Equatorial → Horizontal<br/>az/el for observer"]
H --> I["pg_topocentric result<br/>(az, el, range, range_rate)"]
Stage-by-stage breakdown
Section titled “Stage-by-stage breakdown”-
Heliocentric ecliptic position of the target
VSOP87 (Bretagnon & Francou, 1988) computes the target planet’s position in the heliocentric ecliptic J2000 frame. The output is three Cartesian coordinates in AU.
VSOP87 is a semi-analytical theory: it expands each coordinate as a sum of trigonometric series with polynomial time arguments. The truncated series used in pg_orbit provides ~1 arcsecond accuracy for the inner planets and ~1-2 arcseconds for the outer planets over the period 2000 BCE to 6000 CE.
For the Sun, this stage returns --- the Sun is at the origin of heliocentric coordinates. The Sun’s apparent position is computed by inverting Earth’s heliocentric position.
For the Moon, this stage is replaced by ELP2000-82B (Chapront-Touze & Chapront, 1988), which computes geocentric ecliptic coordinates directly, skipping stage 2.
Code:
src/vsop87.c:GetVsop87Coor(),src/elp82b.c:GetElp82bCoor() -
Heliocentric ecliptic position of Earth
The same VSOP87 call, but for body ID 3 (Earth). This gives Earth’s position in the same frame as the target. Without this, there is no way to compute where the target appears from Earth’s perspective.
Code:
src/vsop87.c:GetVsop87Coor()withbody = 2(VSOP87 uses 0-indexed: Venus=1, Earth=2, Mars=3, etc.) -
Geocentric ecliptic vector
Subtract Earth’s heliocentric position from the target’s:
The result is the target’s position as seen from Earth’s center, still in the ecliptic J2000 frame. For the Moon, ELP2000-82B provides this directly.
Code:
src/planet_funcs.c:planet_observe(), lines computinggeo_ecl_au[3] -
Ecliptic to equatorial rotation
The ecliptic and equatorial planes are tilted relative to each other by the obliquity of the ecliptic. At J2000, this angle is:
The rotation is around the X-axis (the vernal equinox direction, which is shared between both frames):
This uses the J2000 obliquity, not the obliquity of date, because both VSOP87 and the subsequent precession model are referenced to J2000.
Code:
src/planet_funcs.c:ecliptic_to_equatorial()(viaastro_math.h) -
Precession from J2000 to date
The equatorial coordinate system itself rotates slowly due to lunisolar and planetary precession. Right ascension and declination at J2000 must be precessed to the epoch of observation.
pg_orbit uses the IAU 1976 precession model (Lieske et al., 1977), which expresses the three Euler angles , , and as cubic polynomials in centuries from J2000:
The full rotation matrix transforms J2000 equatorial to equatorial of date.
Code:
src/precession.c:precess_j2000_to_date() -
Sidereal time and hour angle
To convert from the celestial sphere to the observer’s local sky, we need the observer’s relationship to the vernal equinox. This requires two quantities:
- GMST (Greenwich Mean Sidereal Time): the hour angle of the vernal equinox at Greenwich, computed from the Julian date using Vallado Eq. 3-47 (IAU 1982 model).
- LST (Local Sidereal Time): , where is the observer’s east longitude in radians.
- Hour angle: , where is the right ascension of date from stage 5.
The GMST formula:
where . The result in seconds is converted to radians: .
Code:
src/sidereal_time.c:gmst_from_jd() -
Equatorial to horizontal
Given hour angle , declination , and observer latitude , compute azimuth and elevation :
Azimuth is measured from north through east (0 = north, 90 = east, 180 = south, 270 = west).
The result is packed into a
pg_topocentricstruct with range computed from the geocentric distance (). Range rate is set to 0.0 for planetary observations (velocity computation is not yet implemented for the VSOP87 pipeline).Code:
src/planet_funcs.c:observe_from_geocentric()
Pipeline variants
Section titled “Pipeline variants”The seven-stage pipeline applies to planets observed via VSOP87. Other observation targets use modified versions.
Stages 1-3 are replaced by a single call to GetElp82bCoor(), which returns geocentric ecliptic coordinates directly. The remaining stages (4-7) are identical.
ELP2000-82B is a semi-analytical lunar theory with ~10 arcsecond accuracy. It accounts for the principal perturbations from the Sun, but not the full set of planetary perturbations included in the DE series.
Each moon theory (L1.2 for Galilean moons, TASS17 for Saturn, GUST86 for Uranus, MarsSat for Mars) computes the moon’s position relative to its parent planet. The pipeline:
- Compute parent planet heliocentric position via VSOP87
- Compute moon position relative to parent in parent-equatorial frame
- Transform to heliocentric ecliptic J2000 (parent offset + frame rotation)
- Proceed from stage 3 of the standard pipeline (geocentric ecliptic)
Satellites use a fundamentally different pipeline. SGP4 outputs TEME (True Equator, Mean Equinox) positions, not heliocentric ecliptic. The satellite pipeline:
- SGP4/SDP4 propagation to TEME position/velocity (WGS-72)
- GMST rotation to ECEF
- ECEF to geodetic (WGS-84) or topocentric via SEZ transform
Stars are effectively at infinite distance. The pipeline:
- J2000 catalog coordinates (RA, Dec)
- Precession to date (IAU 1976)
- Sidereal time and hour angle
- Equatorial to horizontal
No VSOP87 call, no geocentric vector, no distance computation. This makes star observation the fastest pipeline --- 714K observations per second.
Why this pipeline and not another
Section titled “Why this pipeline and not another”Several simplifications are deliberate.
No aberration correction. Annual aberration shifts apparent positions by up to 20 arcseconds, but for observation planning (which quadrant of the sky is Jupiter in tonight?) this is irrelevant. Sub-arcsecond work should use SPICE or Skyfield with DE441.
No light-time iteration. The positions returned are geometric, not apparent. Light-time corrections of a few minutes for the outer planets shift the apparent position by a fraction of an arcsecond at most --- again, below the VSOP87 accuracy floor.
No atmospheric refraction. Refraction near the horizon can shift apparent elevation by half a degree. pg_orbit reports geometric elevation; the user must apply refraction corrections for their local conditions if needed. This is a deliberate choice --- refraction depends on temperature, pressure, and humidity that pg_orbit does not model.
Extending the pipeline
Section titled “Extending the pipeline”To add a new observation target, identify which stages change:
| New target | Stages replaced | Stages reused |
|---|---|---|
| New moon theory | 1-3 (new theory + parent VSOP87) | 4-7 |
| New planet ephemeris | 1 (new theory replaces VSOP87) | 2-7 (Earth still VSOP87) |
| Near-Earth asteroid | 1 (Keplerian propagation) | 2-7 |
| Distant star | 1-3 (catalog lookup, no distance) | 5-7 (skip obliquity) |
The modularity of the pipeline means new targets require implementing only the first few stages. The coordinate transformation machinery from stage 4 onward is shared across all targets.