pg_orrery/src/star_funcs.c
Ryan Malloy 0544a78276 pg_orbit 0.2.0: Full solar system computation at the SQL layer
Phase 1 — Stars, comets, Keplerian propagation:
- star_observe() / star_observe_safe(): fixed star alt/az via IAU 1976
  precession, equatorial-to-horizontal transform
- kepler_propagate(): two-body Keplerian orbit propagation for
  elliptic, parabolic, and hyperbolic orbits
- comet_observe(): observe comets/asteroids from orbital elements
- heliocentric type: ecliptic J2000 position (x, y, z in AU)

Phase 2 — VSOP87 planets, ELP82B Moon, Sun:
- planet_heliocentric(): VSOP87 heliocentric ecliptic J2000 positions
  for Mercury through Neptune (Bretagnon & Francou, MIT)
- planet_observe(): full observation pipeline for any planet
- sun_observe(): Sun position from negated Earth VSOP87
- moon_observe(): ELP2000-82B lunar position (Chapront-Touzé, MIT)
- Clean-room precession (IAU 2006) and sidereal time (IERS 2010)
- elliptic_to_rectangular utility (Stellarium, MIT)

All Stellarium extractions are MIT-licensed, thread-safe (static
caching removed for PARALLEL SAFE), zero external data files.

All 9 regression tests pass (90ms total).
2026-02-16 01:36:27 -07:00

123 lines
3.6 KiB
C

/*
* star_funcs.c -- Star and fixed-position object observation
*
* Takes J2000 catalog coordinates (RA in hours, Dec in degrees),
* applies IAU 1976 precession to date of observation, computes
* local hour angle, and converts to topocentric azimuth/elevation.
*
* Range and range_rate are zero -- stars are effectively at infinity.
* For objects with known proper motion, apply it to (RA, Dec) before
* calling star_observe.
*/
#include "postgres.h"
#include "fmgr.h"
#include "utils/timestamp.h"
#include "types.h"
#include "astro_math.h"
PG_FUNCTION_INFO_V1(star_observe);
PG_FUNCTION_INFO_V1(star_observe_safe);
/*
* star_observe(ra_hours, dec_degrees, observer, timestamptz) -> topocentric
*
* Compute az/el of a fixed celestial object from an observer at a time.
* Uses IAU 1976 precession (~1 arcsecond accuracy for centuries near J2000).
*/
Datum
star_observe(PG_FUNCTION_ARGS)
{
double ra_hours = PG_GETARG_FLOAT8(0);
double dec_deg = PG_GETARG_FLOAT8(1);
pg_observer *obs = (pg_observer *) PG_GETARG_POINTER(2);
int64 ts = PG_GETARG_INT64(3);
double jd;
double ra_j2000, dec_j2000;
double ra_date, dec_date;
double gmst, lst, ha;
double az, el;
pg_topocentric *result;
if (ra_hours < 0.0 || ra_hours >= 24.0)
ereport(ERROR,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("right ascension out of range: %.6f", ra_hours),
errhint("RA must be in [0, 24) hours.")));
if (dec_deg < -90.0 || dec_deg > 90.0)
ereport(ERROR,
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
errmsg("declination out of range: %.6f", dec_deg),
errhint("Declination must be between -90 and +90 degrees.")));
jd = timestamptz_to_jd(ts);
ra_j2000 = ra_hours * (M_PI / 12.0);
dec_j2000 = dec_deg * DEG_TO_RAD;
precess_j2000_to_date(jd, ra_j2000, dec_j2000, &ra_date, &dec_date);
gmst = gmst_from_jd(jd);
lst = gmst + obs->lon;
ha = lst - ra_date;
equatorial_to_horizontal(ha, dec_date, obs->lat, &az, &el);
result = (pg_topocentric *) palloc(sizeof(pg_topocentric));
result->azimuth = az;
result->elevation = el;
result->range_km = 0.0;
result->range_rate = 0.0;
PG_RETURN_POINTER(result);
}
/*
* star_observe_safe -- returns NULL if inputs are out of range.
* For batch queries over star catalogs.
*/
Datum
star_observe_safe(PG_FUNCTION_ARGS)
{
double ra_hours = PG_GETARG_FLOAT8(0);
double dec_deg = PG_GETARG_FLOAT8(1);
pg_observer *obs = (pg_observer *) PG_GETARG_POINTER(2);
int64 ts = PG_GETARG_INT64(3);
double jd;
double ra_j2000, dec_j2000;
double ra_date, dec_date;
double gmst, lst, ha;
double az, el;
pg_topocentric *result;
if (ra_hours < 0.0 || ra_hours >= 24.0)
PG_RETURN_NULL();
if (dec_deg < -90.0 || dec_deg > 90.0)
PG_RETURN_NULL();
jd = timestamptz_to_jd(ts);
ra_j2000 = ra_hours * (M_PI / 12.0);
dec_j2000 = dec_deg * DEG_TO_RAD;
precess_j2000_to_date(jd, ra_j2000, dec_j2000, &ra_date, &dec_date);
gmst = gmst_from_jd(jd);
lst = gmst + obs->lon;
ha = lst - ra_date;
equatorial_to_horizontal(ha, dec_date, obs->lat, &az, &el);
result = (pg_topocentric *) palloc(sizeof(pg_topocentric));
result->azimuth = az;
result->elevation = el;
result->range_km = 0.0;
result->range_rate = 0.0;
PG_RETURN_POINTER(result);
}