An existing product called PG Orbit (a mobile PostgreSQL client) creates a naming conflict. pg_orrery — a database orrery built from Keplerian parameters and SQL instead of brass gears. Build system: control file, Makefile, Dockerfile, docker init script. C source: GUC prefix, PG_FUNCTION_INFO_V1 symbol, header guards, ereport prefixes, comments across ~30 files including vendored SGP4. SQL: all 5 install/migration scripts, function name pg_orrery_ephemeris_info. Tests: 9 SQL suites, 8 expected outputs, standalone DE reader test. Documentation: CLAUDE.md, README.md, DESIGN.md, Starlight site infra, 36 MDX pages, OG renderer, logo SVG, docker-compose, agent threads. All 13 regression suites pass. Docs site builds (37 pages).
200 lines
6.0 KiB
C
200 lines
6.0 KiB
C
/*
|
|
* planet_funcs.c -- VSOP87 planet and ELP82B Moon observation
|
|
*
|
|
* SQL functions for planetary/Sun/Moon position and observation.
|
|
* Wraps VSOP87 (Bretagnon & Francou) for 8 planets + Sun, and
|
|
* ELP2000-82B (Chapront-Touze) for the Moon.
|
|
*
|
|
* The observation pipeline:
|
|
* 1. Heliocentric ecliptic J2000 position (VSOP87 or ELP82B)
|
|
* 2. Geocentric ecliptic (subtract Earth's heliocentric)
|
|
* 3. Ecliptic -> equatorial J2000 (obliquity rotation)
|
|
* 4. Spherical coordinates (RA, Dec, distance)
|
|
* 5. Precession J2000 -> date (IAU 1976, Lieske)
|
|
* 6. Sidereal time -> hour angle -> az/el
|
|
* 7. Return topocentric result
|
|
*/
|
|
|
|
#include "postgres.h"
|
|
#include "fmgr.h"
|
|
#include "funcapi.h"
|
|
#include "utils/timestamp.h"
|
|
#include "types.h"
|
|
#include "astro_math.h"
|
|
#include "vsop87.h"
|
|
#include "elp82b.h"
|
|
#include <math.h>
|
|
|
|
PG_FUNCTION_INFO_V1(planet_heliocentric);
|
|
PG_FUNCTION_INFO_V1(planet_observe);
|
|
PG_FUNCTION_INFO_V1(sun_observe);
|
|
PG_FUNCTION_INFO_V1(moon_observe);
|
|
|
|
|
|
/*
|
|
* observe_from_geocentric() is now in astro_math.h as a static inline,
|
|
* shared by planet_funcs.c, moon_funcs.c, and de_funcs.c.
|
|
*/
|
|
|
|
|
|
/* ================================================================
|
|
* planet_heliocentric(body_id int, timestamptz) -> heliocentric
|
|
*
|
|
* Returns the heliocentric ecliptic J2000 position of a planet
|
|
* (or the Sun, as the barycenter proxy at (0,0,0)).
|
|
*
|
|
* Body IDs: 0=Sun, 1=Mercury, ..., 8=Neptune
|
|
* ================================================================
|
|
*/
|
|
Datum
|
|
planet_heliocentric(PG_FUNCTION_ARGS)
|
|
{
|
|
int32 body_id = PG_GETARG_INT32(0);
|
|
int64 ts = PG_GETARG_INT64(1);
|
|
double jd;
|
|
double xyz[6];
|
|
int vsop_body;
|
|
pg_heliocentric *result;
|
|
|
|
if (body_id == BODY_SUN)
|
|
{
|
|
/* Sun is at the origin of heliocentric coordinates */
|
|
result = (pg_heliocentric *) palloc(sizeof(pg_heliocentric));
|
|
result->x = 0.0;
|
|
result->y = 0.0;
|
|
result->z = 0.0;
|
|
PG_RETURN_POINTER(result);
|
|
}
|
|
|
|
if (body_id < BODY_MERCURY || body_id > BODY_NEPTUNE)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
|
|
errmsg("invalid body_id %d: must be 0 (Sun) or 1-8 (Mercury-Neptune)",
|
|
body_id)));
|
|
|
|
jd = timestamptz_to_jd(ts);
|
|
vsop_body = body_id - 1; /* pg_orrery 1-based -> VSOP87 0-based */
|
|
GetVsop87Coor(jd, vsop_body, xyz);
|
|
|
|
result = (pg_heliocentric *) palloc(sizeof(pg_heliocentric));
|
|
result->x = xyz[0];
|
|
result->y = xyz[1];
|
|
result->z = xyz[2];
|
|
|
|
PG_RETURN_POINTER(result);
|
|
}
|
|
|
|
|
|
/* ================================================================
|
|
* planet_observe(body_id int, observer, timestamptz) -> topocentric
|
|
*
|
|
* Full pipeline: VSOP87 position -> geocentric -> equatorial ->
|
|
* precession -> topocentric az/el.
|
|
*
|
|
* Body IDs: 1=Mercury, ..., 8=Neptune (not Sun or Moon)
|
|
* ================================================================
|
|
*/
|
|
Datum
|
|
planet_observe(PG_FUNCTION_ARGS)
|
|
{
|
|
int32 body_id = PG_GETARG_INT32(0);
|
|
pg_observer *obs = (pg_observer *) PG_GETARG_POINTER(1);
|
|
int64 ts = PG_GETARG_INT64(2);
|
|
double jd;
|
|
double earth_xyz[6];
|
|
double planet_xyz[6];
|
|
double geo_ecl[3];
|
|
int vsop_body;
|
|
pg_topocentric *result;
|
|
|
|
if (body_id < BODY_MERCURY || body_id > BODY_NEPTUNE)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
|
|
errmsg("planet_observe: body_id %d must be 1-8 (Mercury-Neptune)",
|
|
body_id)));
|
|
|
|
if (body_id == BODY_EARTH)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg("cannot observe Earth from Earth")));
|
|
|
|
jd = timestamptz_to_jd(ts);
|
|
|
|
/* Earth's heliocentric position */
|
|
GetVsop87Coor(jd, 2, earth_xyz); /* VSOP87 body 2 = Earth */
|
|
|
|
/* Target planet heliocentric position */
|
|
vsop_body = body_id - 1;
|
|
GetVsop87Coor(jd, vsop_body, planet_xyz);
|
|
|
|
/* Geocentric ecliptic = planet - Earth */
|
|
geo_ecl[0] = planet_xyz[0] - earth_xyz[0];
|
|
geo_ecl[1] = planet_xyz[1] - earth_xyz[1];
|
|
geo_ecl[2] = planet_xyz[2] - earth_xyz[2];
|
|
|
|
result = (pg_topocentric *) palloc(sizeof(pg_topocentric));
|
|
observe_from_geocentric(geo_ecl, jd, obs, result);
|
|
|
|
PG_RETURN_POINTER(result);
|
|
}
|
|
|
|
|
|
/* ================================================================
|
|
* sun_observe(observer, timestamptz) -> topocentric
|
|
*
|
|
* The Sun's geocentric position is the negation of Earth's
|
|
* heliocentric VSOP87 position.
|
|
* ================================================================
|
|
*/
|
|
Datum
|
|
sun_observe(PG_FUNCTION_ARGS)
|
|
{
|
|
pg_observer *obs = (pg_observer *) PG_GETARG_POINTER(0);
|
|
int64 ts = PG_GETARG_INT64(1);
|
|
double jd;
|
|
double earth_xyz[6];
|
|
double geo_ecl[3];
|
|
pg_topocentric *result;
|
|
|
|
jd = timestamptz_to_jd(ts);
|
|
|
|
/* Earth heliocentric -> Sun geocentric by negation */
|
|
GetVsop87Coor(jd, 2, earth_xyz);
|
|
geo_ecl[0] = -earth_xyz[0];
|
|
geo_ecl[1] = -earth_xyz[1];
|
|
geo_ecl[2] = -earth_xyz[2];
|
|
|
|
result = (pg_topocentric *) palloc(sizeof(pg_topocentric));
|
|
observe_from_geocentric(geo_ecl, jd, obs, result);
|
|
|
|
PG_RETURN_POINTER(result);
|
|
}
|
|
|
|
|
|
/* ================================================================
|
|
* moon_observe(observer, timestamptz) -> topocentric
|
|
*
|
|
* ELP2000-82B returns geocentric ecliptic J2000 Moon position.
|
|
* No Earth subtraction needed.
|
|
* ================================================================
|
|
*/
|
|
Datum
|
|
moon_observe(PG_FUNCTION_ARGS)
|
|
{
|
|
pg_observer *obs = (pg_observer *) PG_GETARG_POINTER(0);
|
|
int64 ts = PG_GETARG_INT64(1);
|
|
double jd;
|
|
double moon_ecl[3];
|
|
pg_topocentric *result;
|
|
|
|
jd = timestamptz_to_jd(ts);
|
|
|
|
/* Moon geocentric ecliptic J2000 in AU */
|
|
GetElp82bCoor(jd, moon_ecl);
|
|
|
|
result = (pg_topocentric *) palloc(sizeof(pg_topocentric));
|
|
observe_from_geocentric(moon_ecl, jd, obs, result);
|
|
|
|
PG_RETURN_POINTER(result);
|
|
}
|