Clean-room DE binary reader (~400 lines C) with Chebyshev/Clenshaw evaluation — no GPL dependency on jpl_eph. Per-backend lazy initialization preserves PARALLEL SAFE. Existing VSOP87/ELP82B functions stay IMMUTABLE; new _de() variants are STABLE with automatic fallback to compiled-in ephemerides on any DE failure. Implementation: - de_reader.c: header parse, record seek, Clenshaw recurrence - eph_provider.c: GUC (pg_orbit.ephemeris_path), lazy init, ICRS-to-ecliptic frame rotation, on_proc_exit cleanup - de_funcs.c: 11 new SQL functions (_de variants + diagnostics) - Constant chain of custody rules 6-8 (frame rotation, same-provider, AU consistency) Extract observe_from_geocentric() to astro_math.h for shared use by planet_funcs.c, moon_funcs.c, and de_funcs.c. 57 → 68 functions, 11 → 12 regression test suites, all passing.
221 lines
7.1 KiB
C
221 lines
7.1 KiB
C
/*
|
|
* moon_funcs.c -- Planetary moon observation
|
|
*
|
|
* SQL functions for observing moons of Jupiter, Saturn, Uranus, and Mars.
|
|
* Each moon's position is computed in the VSOP87 ecliptic J2000 frame
|
|
* relative to its parent planet, then combined with the parent's
|
|
* heliocentric position to get geocentric az/el.
|
|
*
|
|
* Pipeline for each moon:
|
|
* 1. Parent planet heliocentric position (VSOP87)
|
|
* 2. Moon position relative to parent (L12/TASS17/GUST86/MARSSAT)
|
|
* 3. Moon heliocentric = parent + moon_relative
|
|
* 4. Moon geocentric = moon_heliocentric - Earth_heliocentric
|
|
* 5. Ecliptic -> equatorial -> precess -> az/el
|
|
*/
|
|
|
|
#include "postgres.h"
|
|
#include "fmgr.h"
|
|
#include "funcapi.h"
|
|
#include "utils/timestamp.h"
|
|
#include "types.h"
|
|
#include "astro_math.h"
|
|
#include "vsop87.h"
|
|
#include "l12.h"
|
|
#include "tass17.h"
|
|
#include "gust86.h"
|
|
#include "marssat.h"
|
|
#include <math.h>
|
|
|
|
PG_FUNCTION_INFO_V1(galilean_observe);
|
|
PG_FUNCTION_INFO_V1(saturn_moon_observe);
|
|
PG_FUNCTION_INFO_V1(uranus_moon_observe);
|
|
PG_FUNCTION_INFO_V1(mars_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.
|
|
*/
|
|
|
|
|
|
/* ================================================================
|
|
* Internal: common pattern for all planetary moons
|
|
*
|
|
* Given: moon position relative to parent (VSOP87 ecliptic J2000, AU)
|
|
* parent's VSOP87 body index (0-based)
|
|
* observer, JD
|
|
*
|
|
* Computes geocentric position and returns topocentric az/el.
|
|
* ================================================================
|
|
*/
|
|
static void
|
|
observe_planetary_moon(const double moon_rel[3], int vsop_parent,
|
|
double jd, const pg_observer *obs,
|
|
pg_topocentric *result)
|
|
{
|
|
double parent_xyz[6];
|
|
double earth_xyz[6];
|
|
double geo_ecl[3];
|
|
|
|
/* Parent planet heliocentric */
|
|
GetVsop87Coor(jd, vsop_parent, parent_xyz);
|
|
|
|
/* Earth heliocentric */
|
|
GetVsop87Coor(jd, 2, earth_xyz); /* VSOP87 body 2 = Earth */
|
|
|
|
/* Moon geocentric = (parent + moon_relative) - Earth */
|
|
geo_ecl[0] = (parent_xyz[0] + moon_rel[0]) - earth_xyz[0];
|
|
geo_ecl[1] = (parent_xyz[1] + moon_rel[1]) - earth_xyz[1];
|
|
geo_ecl[2] = (parent_xyz[2] + moon_rel[2]) - earth_xyz[2];
|
|
|
|
observe_from_geocentric(geo_ecl, jd, obs, result);
|
|
}
|
|
|
|
|
|
/* ================================================================
|
|
* galilean_observe(body_id int, observer, timestamptz) -> topocentric
|
|
*
|
|
* Observe a Galilean moon of Jupiter.
|
|
* Body IDs: 0=Io, 1=Europa, 2=Ganymede, 3=Callisto
|
|
*
|
|
* Uses L1.2 theory (Lainey, Duriez & Vienne) for moon positions
|
|
* and VSOP87 for Jupiter and Earth.
|
|
* ================================================================
|
|
*/
|
|
Datum
|
|
galilean_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 moon_xyz[3];
|
|
pg_topocentric *result;
|
|
|
|
if (body_id < L12_IO || body_id > L12_CALLISTO)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
|
|
errmsg("galilean_observe: body_id %d must be 0-3 (Io, Europa, Ganymede, Callisto)",
|
|
body_id)));
|
|
|
|
jd = timestamptz_to_jd(ts);
|
|
|
|
/* Moon position relative to Jupiter, VSOP87 ecliptic J2000, AU */
|
|
GetL12Coor(jd, body_id, moon_xyz, NULL);
|
|
|
|
result = (pg_topocentric *) palloc(sizeof(pg_topocentric));
|
|
observe_planetary_moon(moon_xyz, 4, jd, obs, result); /* VSOP87 body 4 = Jupiter */
|
|
|
|
PG_RETURN_POINTER(result);
|
|
}
|
|
|
|
|
|
/* ================================================================
|
|
* saturn_moon_observe(body_id int, observer, timestamptz) -> topocentric
|
|
*
|
|
* Observe a moon of Saturn.
|
|
* Body IDs: 0=Mimas, 1=Enceladus, 2=Tethys, 3=Dione,
|
|
* 4=Rhea, 5=Titan, 6=Iapetus, 7=Hyperion
|
|
*
|
|
* Uses TASS 1.7 theory (Vienne & Duriez).
|
|
* ================================================================
|
|
*/
|
|
Datum
|
|
saturn_moon_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 moon_xyz[3];
|
|
pg_topocentric *result;
|
|
|
|
if (body_id < TASS17_MIMAS || body_id > TASS17_HYPERION)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
|
|
errmsg("saturn_moon_observe: body_id %d must be 0-7 (Mimas-Hyperion)",
|
|
body_id)));
|
|
|
|
jd = timestamptz_to_jd(ts);
|
|
|
|
GetTass17Coor(jd, body_id, moon_xyz, NULL);
|
|
|
|
result = (pg_topocentric *) palloc(sizeof(pg_topocentric));
|
|
observe_planetary_moon(moon_xyz, 5, jd, obs, result); /* VSOP87 body 5 = Saturn */
|
|
|
|
PG_RETURN_POINTER(result);
|
|
}
|
|
|
|
|
|
/* ================================================================
|
|
* uranus_moon_observe(body_id int, observer, timestamptz) -> topocentric
|
|
*
|
|
* Observe a moon of Uranus.
|
|
* Body IDs: 0=Miranda, 1=Ariel, 2=Umbriel, 3=Titania, 4=Oberon
|
|
*
|
|
* Uses GUST86 theory (Laskar & Jacobson).
|
|
* ================================================================
|
|
*/
|
|
Datum
|
|
uranus_moon_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 moon_xyz[3];
|
|
pg_topocentric *result;
|
|
|
|
if (body_id < GUST86_MIRANDA || body_id > GUST86_OBERON)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
|
|
errmsg("uranus_moon_observe: body_id %d must be 0-4 (Miranda-Oberon)",
|
|
body_id)));
|
|
|
|
jd = timestamptz_to_jd(ts);
|
|
|
|
GetGust86Coor(jd, body_id, moon_xyz, NULL);
|
|
|
|
result = (pg_topocentric *) palloc(sizeof(pg_topocentric));
|
|
observe_planetary_moon(moon_xyz, 6, jd, obs, result); /* VSOP87 body 6 = Uranus */
|
|
|
|
PG_RETURN_POINTER(result);
|
|
}
|
|
|
|
|
|
/* ================================================================
|
|
* mars_moon_observe(body_id int, observer, timestamptz) -> topocentric
|
|
*
|
|
* Observe a moon of Mars.
|
|
* Body IDs: 0=Phobos, 1=Deimos
|
|
*
|
|
* Uses MarsSat theory (Lainey, 2007).
|
|
* ================================================================
|
|
*/
|
|
Datum
|
|
mars_moon_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 moon_xyz[3];
|
|
pg_topocentric *result;
|
|
|
|
if (body_id < MARS_SAT_PHOBOS || body_id > MARS_SAT_DEIMOS)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
|
|
errmsg("mars_moon_observe: body_id %d must be 0-1 (Phobos, Deimos)",
|
|
body_id)));
|
|
|
|
jd = timestamptz_to_jd(ts);
|
|
|
|
GetMarsSatCoor(jd, body_id, moon_xyz, NULL);
|
|
|
|
result = (pg_topocentric *) palloc(sizeof(pg_topocentric));
|
|
observe_planetary_moon(moon_xyz, 3, jd, obs, result); /* VSOP87 body 3 = Mars */
|
|
|
|
PG_RETURN_POINTER(result);
|
|
}
|