v0.15.0: constellation full name lookup, rise/set status diagnostics
constellation_full_name(text) returns full IAU name from 3-letter abbreviation (88-entry static table, IMMUTABLE). Returns NULL for invalid input — composable with constellation() in queries. Three rise_set_status functions classify body visibility as 'rises_and_sets', 'circumpolar', or 'never_rises' by sampling elevation at 48 points across 24h. Separate diagnostic path — called only when rise/set returns NULL, zero cost in normal case. 147 → 151 SQL objects. 25 → 26 regression suites. All pass.
This commit is contained in:
parent
e720e0fd25
commit
501872d45d
25
CLAUDE.md
25
CLAUDE.md
@ -1,9 +1,9 @@
|
||||
# pg_orrery — A Database Orrery for PostgreSQL
|
||||
|
||||
## What This Is
|
||||
A database orrery — celestial mechanics types and functions for PostgreSQL. Native C extension using PGXS, 147 SQL objects (131 user-visible functions + 16 GiST support), 9 custom types, covering satellites (SGP4/SDP4), planets (VSOP87 + optional JPL DE441), Moon (ELP2000-82B), 19 planetary moons (L1.2/TASS17/GUST86/MarsSat), stars (with proper motion and annual parallax), comets, asteroids (MPC catalog), Jupiter radio bursts, interplanetary Lambert transfers, equatorial RA/Dec coordinates with GiST-indexed angular separation, atmospheric refraction, annual stellar aberration, light-time correction, rise/set prediction (geometric + refracted), and IAU constellation identification (Roman 1987).
|
||||
A database orrery — celestial mechanics types and functions for PostgreSQL. Native C extension using PGXS, 151 SQL objects (135 user-visible functions + 16 GiST support), 9 custom types, covering satellites (SGP4/SDP4), planets (VSOP87 + optional JPL DE441), Moon (ELP2000-82B), 19 planetary moons (L1.2/TASS17/GUST86/MarsSat), stars (with proper motion and annual parallax), comets, asteroids (MPC catalog), Jupiter radio bursts, interplanetary Lambert transfers, equatorial RA/Dec coordinates with GiST-indexed angular separation, atmospheric refraction, annual stellar aberration, light-time correction, rise/set prediction (geometric + refracted) with status diagnostics, and IAU constellation identification with full name lookup (Roman 1987).
|
||||
|
||||
**Current version:** 0.14.0
|
||||
**Current version:** 0.15.0
|
||||
**Repository:** https://git.supported.systems/warehack.ing/pg_orrery
|
||||
**Documentation:** https://pg-orrery.warehack.ing
|
||||
|
||||
@ -11,7 +11,7 @@ A database orrery — celestial mechanics types and functions for PostgreSQL. Na
|
||||
```bash
|
||||
make PG_CONFIG=/usr/bin/pg_config # Compile with PGXS
|
||||
sudo make install PG_CONFIG=/usr/bin/pg_config # Install extension
|
||||
make installcheck PG_CONFIG=/usr/bin/pg_config # Run 25 regression test suites
|
||||
make installcheck PG_CONFIG=/usr/bin/pg_config # Run 26 regression test suites
|
||||
```
|
||||
|
||||
Requires: PostgreSQL 17 development headers, GCC, Make.
|
||||
@ -27,7 +27,7 @@ Image: `git.supported.systems/warehack.ing/pg_orrery:pg17`
|
||||
|
||||
## Project Layout
|
||||
```
|
||||
pg_orrery.control # Extension metadata (version 0.14.0)
|
||||
pg_orrery.control # Extension metadata (version 0.15.0)
|
||||
Makefile # PGXS build + Docker targets
|
||||
sql/
|
||||
pg_orrery--0.1.0.sql # v0.1.0: satellite types/functions/operators
|
||||
@ -44,6 +44,7 @@ sql/
|
||||
pg_orrery--0.12.0.sql # v0.12.0: equatorial GiST, DE moon equatorial (132 objects)
|
||||
pg_orrery--0.13.0.sql # v0.13.0: nutation, make_equatorial, rise/set (141 objects)
|
||||
pg_orrery--0.14.0.sql # v0.14.0: refracted rise/set, constellation ID (147 objects)
|
||||
pg_orrery--0.15.0.sql # v0.15.0: constellation full name, rise/set status (151 objects)
|
||||
pg_orrery--0.1.0--0.2.0.sql # Migration: v0.1.0 → v0.2.0 (adds solar system)
|
||||
pg_orrery--0.2.0--0.3.0.sql # Migration: v0.2.0 → v0.3.0 (adds DE ephemeris)
|
||||
pg_orrery--0.3.0--0.4.0.sql # Migration: v0.3.0 → v0.4.0
|
||||
@ -57,6 +58,7 @@ sql/
|
||||
pg_orrery--0.11.0--0.12.0.sql # Migration: v0.11.0 → v0.12.0 (equatorial GiST, DE moon equatorial)
|
||||
pg_orrery--0.12.0--0.13.0.sql # Migration: v0.12.0 → v0.13.0 (nutation, make_equatorial, rise/set)
|
||||
pg_orrery--0.13.0--0.14.0.sql # Migration: v0.13.0 → v0.14.0 (refracted rise/set, constellation ID)
|
||||
pg_orrery--0.14.0--0.15.0.sql # Migration: v0.14.0 → v0.15.0 (constellation full name, rise/set status)
|
||||
src/
|
||||
pg_orrery.c # PG_MODULE_MAGIC + _PG_init() (GUC registration)
|
||||
types.h # All struct definitions + constants + DE body ID mapping
|
||||
@ -110,7 +112,7 @@ src/
|
||||
PROVENANCE.md # Vendoring decision, modifications, verification
|
||||
LICENSE # MIT license (Bill Gray / Project Pluto)
|
||||
test/
|
||||
sql/ # 25 regression test suites
|
||||
sql/ # 26 regression test suites
|
||||
expected/ # Expected output
|
||||
data/vallado_518.json # 518 Vallado test vectors (AIAA 2006-6753-Rev1)
|
||||
docs/
|
||||
@ -137,7 +139,7 @@ All types are fixed-size, `STORAGE = plain`, `ALIGNMENT = double`. No TOAST over
|
||||
| `orbital_elements` | 72 | Classical Keplerian elements for comets/asteroids (epoch, q, e, inc, omega, Omega, tp, H, G) |
|
||||
| `equatorial` | 24 | Apparent RA (hours), Dec (degrees), distance (km) — of date |
|
||||
|
||||
## Function Domains (147 SQL objects)
|
||||
## Function Domains (151 SQL objects)
|
||||
|
||||
| Domain | Theory | Key Functions | Count |
|
||||
|--------|--------|---------------|-------|
|
||||
@ -154,8 +156,8 @@ All types are fixed-size, `STORAGE = plain`, `ALIGNMENT = double`. No TOAST over
|
||||
| DE ephemeris | JPL DE440/441 (optional) | `planet_observe_de()`, `*_equatorial_de()`, `*_apparent_de()` | 23 |
|
||||
| GiST index (TLE) | Altitude-band approximation | `&&` (overlap), `<->` (distance) | 8 |
|
||||
| GiST index (equatorial) | Spherical bounding box | `<->` (KNN ordering) | 8 |
|
||||
| Rise/set | Bisection (60s scan) | `planet_next_rise()`, `sun_next_rise_refracted()`, `moon_next_set_refracted()` | 12 |
|
||||
| Constellation | Roman (1987) CDS VI/42 | `constellation()` (equatorial + RA/Dec overloads) | 2 |
|
||||
| Rise/set | Bisection (60s scan) | `planet_next_rise()`, `sun_next_rise_refracted()`, `*_rise_set_status()` | 15 |
|
||||
| Constellation | Roman (1987) CDS VI/42 | `constellation()`, `constellation_full_name()` | 3 |
|
||||
| Diagnostics | -- | `pg_orrery_ephemeris_info()` | 1 |
|
||||
|
||||
All functions are `PARALLEL SAFE`. VSOP87/ELP82B functions are `IMMUTABLE` (compiled-in coefficients). DE functions are `STABLE` (external file dependency).
|
||||
@ -289,7 +291,7 @@ All numerical logic is byte-identical to upstream. Verified against 518 Vallado
|
||||
|
||||
## Testing
|
||||
|
||||
25 regression test suites via `make installcheck`:
|
||||
26 regression test suites via `make installcheck`:
|
||||
|
||||
| Suite | What it tests |
|
||||
|-------|--------------|
|
||||
@ -318,10 +320,11 @@ All numerical logic is byte-identical to upstream. Verified against 518 Vallado
|
||||
| v013_features | Nutation correction, make_equatorial constructor |
|
||||
| rise_set | Planet/Sun/Moon rise/set (geometric + refracted), circumpolar, polar night |
|
||||
| constellation | Roman (1987) boundary lookup, known stars, solar system objects, edge cases |
|
||||
| v015_features | constellation_full_name lookup, rise_set_status diagnostics (circumpolar/never_rises) |
|
||||
|
||||
### PG Version Matrix
|
||||
|
||||
Test all 25 regression suites + DE reader unit test across PostgreSQL 14-18 using Docker:
|
||||
Test all 26 regression suites + DE reader unit test across PostgreSQL 14-18 using Docker:
|
||||
|
||||
```bash
|
||||
make test-matrix # Full matrix (PG 14-18)
|
||||
@ -347,7 +350,7 @@ Logs saved to `test/matrix-logs/pg${ver}.log`. The script reuses the Dockerfile
|
||||
|
||||
Starlight docs at `docs/` — 44+ MDX pages covering all domains.
|
||||
|
||||
Sections: Getting Started, Guides (9 domain walkthroughs incl. DE ephemeris), Workflow Translation (Skyfield/Horizons/GMAT/Radio Jupiter Pro comparisons), Reference (all 147 SQL objects incl. DE variants, equatorial GiST, refraction, rise/set, constellation), Architecture (Hamilton's principles, constant custody, observation pipeline), Performance (benchmarks).
|
||||
Sections: Getting Started, Guides (9 domain walkthroughs incl. DE ephemeris), Workflow Translation (Skyfield/Horizons/GMAT/Radio Jupiter Pro comparisons), Reference (all 151 SQL objects incl. DE variants, equatorial GiST, refraction, rise/set, constellation), Architecture (Hamilton's principles, constant custody, observation pipeline), Performance (benchmarks).
|
||||
|
||||
### Local Development
|
||||
```bash
|
||||
|
||||
6
Makefile
6
Makefile
@ -12,7 +12,8 @@ DATA = sql/pg_orrery--0.1.0.sql sql/pg_orrery--0.2.0.sql sql/pg_orrery--0.1.0--0
|
||||
sql/pg_orrery--0.11.0.sql sql/pg_orrery--0.10.0--0.11.0.sql \
|
||||
sql/pg_orrery--0.12.0.sql sql/pg_orrery--0.11.0--0.12.0.sql \
|
||||
sql/pg_orrery--0.13.0.sql sql/pg_orrery--0.12.0--0.13.0.sql \
|
||||
sql/pg_orrery--0.14.0.sql sql/pg_orrery--0.13.0--0.14.0.sql
|
||||
sql/pg_orrery--0.14.0.sql sql/pg_orrery--0.13.0--0.14.0.sql \
|
||||
sql/pg_orrery--0.15.0.sql sql/pg_orrery--0.14.0--0.15.0.sql
|
||||
|
||||
# Our extension C sources
|
||||
OBJS = src/pg_orrery.o src/tle_type.o src/eci_type.o src/observer_type.o \
|
||||
@ -50,7 +51,8 @@ REGRESS = tle_parse sgp4_propagate coord_transforms pass_prediction gist_index c
|
||||
aberration v011_features vallado_518 \
|
||||
gist_equatorial v012_features \
|
||||
v013_features rise_set \
|
||||
constellation
|
||||
constellation \
|
||||
v015_features
|
||||
REGRESS_OPTS = --inputdir=test
|
||||
|
||||
# Pure C — no C++ runtime needed. LAPACK for OD solver (dgelss_).
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
comment = 'A database orrery — celestial mechanics types and functions for PostgreSQL'
|
||||
default_version = '0.14.0'
|
||||
default_version = '0.15.0'
|
||||
module_pathname = '$libdir/pg_orrery'
|
||||
relocatable = true
|
||||
|
||||
33
sql/pg_orrery--0.14.0--0.15.0.sql
Normal file
33
sql/pg_orrery--0.14.0--0.15.0.sql
Normal file
@ -0,0 +1,33 @@
|
||||
-- pg_orrery 0.14.0 -> 0.15.0 migration
|
||||
--
|
||||
-- Adds: constellation_full_name (1 function),
|
||||
-- rise/set status diagnostics (3 functions).
|
||||
|
||||
-- ============================================================
|
||||
-- Constellation full name lookup
|
||||
-- ============================================================
|
||||
|
||||
CREATE FUNCTION constellation_full_name(abbr text) RETURNS text
|
||||
AS 'MODULE_PATHNAME', 'constellation_full_name_from_abbr'
|
||||
LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
||||
COMMENT ON FUNCTION constellation_full_name(text) IS
|
||||
'Full IAU constellation name from 3-letter abbreviation. Returns NULL for invalid abbreviation.';
|
||||
|
||||
-- ============================================================
|
||||
-- Rise/set status diagnostics
|
||||
-- ============================================================
|
||||
|
||||
CREATE FUNCTION sun_rise_set_status(obs observer, t timestamptz) RETURNS text
|
||||
AS 'MODULE_PATHNAME' LANGUAGE C STABLE STRICT PARALLEL SAFE;
|
||||
COMMENT ON FUNCTION sun_rise_set_status(observer, timestamptz) IS
|
||||
'Classify Sun visibility: rises_and_sets, circumpolar, or never_rises.';
|
||||
|
||||
CREATE FUNCTION moon_rise_set_status(obs observer, t timestamptz) RETURNS text
|
||||
AS 'MODULE_PATHNAME' LANGUAGE C STABLE STRICT PARALLEL SAFE;
|
||||
COMMENT ON FUNCTION moon_rise_set_status(observer, timestamptz) IS
|
||||
'Classify Moon visibility: rises_and_sets, circumpolar, or never_rises.';
|
||||
|
||||
CREATE FUNCTION planet_rise_set_status(body_id int4, obs observer, t timestamptz) RETURNS text
|
||||
AS 'MODULE_PATHNAME' LANGUAGE C STABLE STRICT PARALLEL SAFE;
|
||||
COMMENT ON FUNCTION planet_rise_set_status(int4, observer, timestamptz) IS
|
||||
'Classify planet visibility: rises_and_sets, circumpolar, or never_rises. Body IDs 1-8 (Mercury-Neptune).';
|
||||
1595
sql/pg_orrery--0.15.0.sql
Normal file
1595
sql/pg_orrery--0.15.0.sql
Normal file
File diff suppressed because it is too large
Load Diff
@ -376,3 +376,96 @@ const roman_boundary roman_boundaries[] = {
|
||||
};
|
||||
|
||||
const int roman_boundary_count = sizeof(roman_boundaries) / sizeof(roman_boundaries[0]);
|
||||
|
||||
const constellation_name constellation_names[] = {
|
||||
{ "And", "Andromeda" },
|
||||
{ "Ant", "Antlia" },
|
||||
{ "Aps", "Apus" },
|
||||
{ "Aqr", "Aquarius" },
|
||||
{ "Aql", "Aquila" },
|
||||
{ "Ara", "Ara" },
|
||||
{ "Ari", "Aries" },
|
||||
{ "Aur", "Auriga" },
|
||||
{ "Boo", "Bootes" },
|
||||
{ "Cae", "Caelum" },
|
||||
{ "Cam", "Camelopardalis" },
|
||||
{ "Cnc", "Cancer" },
|
||||
{ "CVn", "Canes Venatici" },
|
||||
{ "CMa", "Canis Major" },
|
||||
{ "CMi", "Canis Minor" },
|
||||
{ "Cap", "Capricornus" },
|
||||
{ "Car", "Carina" },
|
||||
{ "Cas", "Cassiopeia" },
|
||||
{ "Cen", "Centaurus" },
|
||||
{ "Cep", "Cepheus" },
|
||||
{ "Cet", "Cetus" },
|
||||
{ "Cha", "Chamaeleon" },
|
||||
{ "Cir", "Circinus" },
|
||||
{ "Col", "Columba" },
|
||||
{ "Com", "Coma Berenices" },
|
||||
{ "CrA", "Corona Australis" },
|
||||
{ "CrB", "Corona Borealis" },
|
||||
{ "Crv", "Corvus" },
|
||||
{ "Crt", "Crater" },
|
||||
{ "Cru", "Crux" },
|
||||
{ "Cyg", "Cygnus" },
|
||||
{ "Del", "Delphinus" },
|
||||
{ "Dor", "Dorado" },
|
||||
{ "Dra", "Draco" },
|
||||
{ "Equ", "Equuleus" },
|
||||
{ "Eri", "Eridanus" },
|
||||
{ "For", "Fornax" },
|
||||
{ "Gem", "Gemini" },
|
||||
{ "Gru", "Grus" },
|
||||
{ "Her", "Hercules" },
|
||||
{ "Hor", "Horologium" },
|
||||
{ "Hya", "Hydra" },
|
||||
{ "Hyi", "Hydrus" },
|
||||
{ "Ind", "Indus" },
|
||||
{ "Lac", "Lacerta" },
|
||||
{ "Leo", "Leo" },
|
||||
{ "LMi", "Leo Minor" },
|
||||
{ "Lep", "Lepus" },
|
||||
{ "Lib", "Libra" },
|
||||
{ "Lup", "Lupus" },
|
||||
{ "Lyn", "Lynx" },
|
||||
{ "Lyr", "Lyra" },
|
||||
{ "Men", "Mensa" },
|
||||
{ "Mic", "Microscopium" },
|
||||
{ "Mon", "Monoceros" },
|
||||
{ "Mus", "Musca" },
|
||||
{ "Nor", "Norma" },
|
||||
{ "Oct", "Octans" },
|
||||
{ "Oph", "Ophiuchus" },
|
||||
{ "Ori", "Orion" },
|
||||
{ "Pav", "Pavo" },
|
||||
{ "Peg", "Pegasus" },
|
||||
{ "Per", "Perseus" },
|
||||
{ "Phe", "Phoenix" },
|
||||
{ "Pic", "Pictor" },
|
||||
{ "Psc", "Pisces" },
|
||||
{ "PsA", "Piscis Austrinus" },
|
||||
{ "Pup", "Puppis" },
|
||||
{ "Pyx", "Pyxis" },
|
||||
{ "Ret", "Reticulum" },
|
||||
{ "Sge", "Sagitta" },
|
||||
{ "Sgr", "Sagittarius" },
|
||||
{ "Sco", "Scorpius" },
|
||||
{ "Scl", "Sculptor" },
|
||||
{ "Sct", "Scutum" },
|
||||
{ "Ser", "Serpens" },
|
||||
{ "Sex", "Sextans" },
|
||||
{ "Tau", "Taurus" },
|
||||
{ "Tel", "Telescopium" },
|
||||
{ "Tri", "Triangulum" },
|
||||
{ "TrA", "Triangulum Australe" },
|
||||
{ "Tuc", "Tucana" },
|
||||
{ "UMa", "Ursa Major" },
|
||||
{ "UMi", "Ursa Minor" },
|
||||
{ "Vel", "Vela" },
|
||||
{ "Vir", "Virgo" },
|
||||
{ "Vol", "Volans" },
|
||||
{ "Vul", "Vulpecula" },
|
||||
};
|
||||
|
||||
const int constellation_name_count = sizeof(constellation_names) / sizeof(constellation_names[0]);
|
||||
|
||||
@ -23,4 +23,13 @@ typedef struct roman_boundary
|
||||
extern const roman_boundary roman_boundaries[];
|
||||
extern const int roman_boundary_count;
|
||||
|
||||
typedef struct constellation_name
|
||||
{
|
||||
char abbr[4]; /* 3-letter IAU abbreviation + null */
|
||||
char full[24]; /* Full IAU name + null (longest: "Triangulum Australe" = 20 chars) */
|
||||
} constellation_name;
|
||||
|
||||
extern const constellation_name constellation_names[];
|
||||
extern const int constellation_name_count;
|
||||
|
||||
#endif /* PG_ORRERY_CONSTELLATION_DATA_H */
|
||||
|
||||
@ -17,6 +17,7 @@
|
||||
|
||||
#include "postgres.h"
|
||||
#include "fmgr.h"
|
||||
#include "varatt.h"
|
||||
#include "utils/builtins.h"
|
||||
|
||||
#include "types.h"
|
||||
@ -26,6 +27,7 @@
|
||||
|
||||
PG_FUNCTION_INFO_V1(constellation_from_equatorial);
|
||||
PG_FUNCTION_INFO_V1(constellation_from_radec);
|
||||
PG_FUNCTION_INFO_V1(constellation_full_name_from_abbr);
|
||||
|
||||
/* B1875.0 epoch as Julian date.
|
||||
* JD(B) = 2415020.31352 + (B - 1900.0) * 365.242198781
|
||||
@ -173,3 +175,35 @@ constellation_from_radec(PG_FUNCTION_ARGS)
|
||||
|
||||
PG_RETURN_TEXT_P(cstring_to_text(abbr));
|
||||
}
|
||||
|
||||
|
||||
/* ================================================================
|
||||
* constellation_full_name(text) -> text
|
||||
*
|
||||
* Returns the full IAU name for a 3-letter abbreviation.
|
||||
* Returns NULL for unrecognized abbreviations (composable in queries).
|
||||
* ================================================================
|
||||
*/
|
||||
Datum
|
||||
constellation_full_name_from_abbr(PG_FUNCTION_ARGS)
|
||||
{
|
||||
text *abbr_text = PG_GETARG_TEXT_PP(0);
|
||||
char abbr[4];
|
||||
int len;
|
||||
int i;
|
||||
|
||||
len = VARSIZE_ANY_EXHDR(abbr_text);
|
||||
if (len < 2 || len > 3)
|
||||
PG_RETURN_NULL();
|
||||
|
||||
memcpy(abbr, VARDATA_ANY(abbr_text), len);
|
||||
abbr[len] = '\0';
|
||||
|
||||
for (i = 0; i < constellation_name_count; i++)
|
||||
{
|
||||
if (strcmp(abbr, constellation_names[i].abbr) == 0)
|
||||
PG_RETURN_TEXT_P(cstring_to_text(constellation_names[i].full));
|
||||
}
|
||||
|
||||
PG_RETURN_NULL();
|
||||
}
|
||||
|
||||
@ -16,6 +16,7 @@
|
||||
#include "postgres.h"
|
||||
#include "fmgr.h"
|
||||
#include "utils/timestamp.h"
|
||||
#include "utils/builtins.h"
|
||||
#include "types.h"
|
||||
#include "astro_math.h"
|
||||
#include "vsop87.h"
|
||||
@ -34,6 +35,9 @@ PG_FUNCTION_INFO_V1(planet_next_rise_refracted);
|
||||
PG_FUNCTION_INFO_V1(planet_next_set_refracted);
|
||||
PG_FUNCTION_INFO_V1(moon_next_rise_refracted);
|
||||
PG_FUNCTION_INFO_V1(moon_next_set_refracted);
|
||||
PG_FUNCTION_INFO_V1(sun_rise_set_status);
|
||||
PG_FUNCTION_INFO_V1(moon_rise_set_status);
|
||||
PG_FUNCTION_INFO_V1(planet_rise_set_status);
|
||||
|
||||
#define COARSE_STEP_JD (60.0 / 86400.0) /* 60 seconds */
|
||||
#define BISECT_TOL_JD (0.1 / 86400.0) /* 0.1 second */
|
||||
@ -193,6 +197,52 @@ find_next_crossing(int body_type, int body_id,
|
||||
}
|
||||
|
||||
|
||||
/* ----------------------------------------------------------------
|
||||
* classify_rise_set -- sample elevation to determine behavior
|
||||
*
|
||||
* Samples body elevation at N_SAMPLES equally-spaced points across
|
||||
* 24 hours starting from start_jd. Classifies:
|
||||
* - All above geometric horizon -> "circumpolar"
|
||||
* - All below geometric horizon -> "never_rises"
|
||||
* - Mixed -> "rises_and_sets"
|
||||
*
|
||||
* Uses geometric horizon (0 deg) for classification — this matches
|
||||
* the NULL contract of the rise/set functions.
|
||||
* ----------------------------------------------------------------
|
||||
*/
|
||||
#define RISE_SET_N_SAMPLES 48
|
||||
|
||||
static const char *
|
||||
classify_rise_set(int body_type, int body_id,
|
||||
const pg_observer *obs, double start_jd)
|
||||
{
|
||||
int above = 0;
|
||||
int below = 0;
|
||||
int i;
|
||||
double step = 1.0 / (double)RISE_SET_N_SAMPLES; /* 24h / N = 30 min */
|
||||
|
||||
for (i = 0; i < RISE_SET_N_SAMPLES; i++)
|
||||
{
|
||||
double jd = start_jd + i * step;
|
||||
double el = elevation_at_jd_body(body_type, body_id, obs, jd);
|
||||
|
||||
if (el > 0.0)
|
||||
above++;
|
||||
else
|
||||
below++;
|
||||
|
||||
/* Early exit: once we have both above and below, it's mixed */
|
||||
if (above > 0 && below > 0)
|
||||
return "rises_and_sets";
|
||||
}
|
||||
|
||||
if (above == RISE_SET_N_SAMPLES)
|
||||
return "circumpolar";
|
||||
else
|
||||
return "never_rises";
|
||||
}
|
||||
|
||||
|
||||
/* ================================================================
|
||||
* planet_next_rise(body_id, observer, timestamptz) -> timestamptz
|
||||
*
|
||||
@ -557,3 +607,80 @@ moon_next_set_refracted(PG_FUNCTION_ARGS)
|
||||
|
||||
PG_RETURN_TIMESTAMPTZ(jd_to_timestamptz(result_jd));
|
||||
}
|
||||
|
||||
|
||||
/* ================================================================
|
||||
* sun_rise_set_status(observer, timestamptz) -> text
|
||||
*
|
||||
* Returns 'rises_and_sets', 'circumpolar', or 'never_rises'.
|
||||
* Call this when sun_next_rise/set returns NULL to find out why.
|
||||
* ================================================================
|
||||
*/
|
||||
Datum
|
||||
sun_rise_set_status(PG_FUNCTION_ARGS)
|
||||
{
|
||||
pg_observer *obs = (pg_observer *) PG_GETARG_POINTER(0);
|
||||
int64 ts = PG_GETARG_INT64(1);
|
||||
double start_jd;
|
||||
const char *status;
|
||||
|
||||
start_jd = timestamptz_to_jd(ts);
|
||||
status = classify_rise_set(BTYPE_SUN, 0, obs, start_jd);
|
||||
|
||||
PG_RETURN_TEXT_P(cstring_to_text(status));
|
||||
}
|
||||
|
||||
|
||||
/* ================================================================
|
||||
* moon_rise_set_status(observer, timestamptz) -> text
|
||||
*
|
||||
* Returns 'rises_and_sets', 'circumpolar', or 'never_rises'.
|
||||
* ================================================================
|
||||
*/
|
||||
Datum
|
||||
moon_rise_set_status(PG_FUNCTION_ARGS)
|
||||
{
|
||||
pg_observer *obs = (pg_observer *) PG_GETARG_POINTER(0);
|
||||
int64 ts = PG_GETARG_INT64(1);
|
||||
double start_jd;
|
||||
const char *status;
|
||||
|
||||
start_jd = timestamptz_to_jd(ts);
|
||||
status = classify_rise_set(BTYPE_MOON, 0, obs, start_jd);
|
||||
|
||||
PG_RETURN_TEXT_P(cstring_to_text(status));
|
||||
}
|
||||
|
||||
|
||||
/* ================================================================
|
||||
* planet_rise_set_status(body_id, observer, timestamptz) -> text
|
||||
*
|
||||
* Returns 'rises_and_sets', 'circumpolar', or 'never_rises'.
|
||||
* Body IDs: 1=Mercury, ..., 8=Neptune (not Sun, Earth, or Moon).
|
||||
* ================================================================
|
||||
*/
|
||||
Datum
|
||||
planet_rise_set_status(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 start_jd;
|
||||
const char *status;
|
||||
|
||||
if (body_id < BODY_MERCURY || body_id > BODY_NEPTUNE)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
|
||||
errmsg("planet_rise_set_status: 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")));
|
||||
|
||||
start_jd = timestamptz_to_jd(ts);
|
||||
status = classify_rise_set(BTYPE_PLANET, body_id, obs, start_jd);
|
||||
|
||||
PG_RETURN_TEXT_P(cstring_to_text(status));
|
||||
}
|
||||
|
||||
204
test/expected/v015_features.out
Normal file
204
test/expected/v015_features.out
Normal file
@ -0,0 +1,204 @@
|
||||
-- v015_features.sql -- Tests for v0.15.0: constellation_full_name + rise_set_status
|
||||
--
|
||||
-- Verifies the constellation full name lookup and the rise/set
|
||||
-- status diagnostic functions.
|
||||
CREATE EXTENSION IF NOT EXISTS pg_orrery;
|
||||
NOTICE: extension "pg_orrery" already exists, skipping
|
||||
-- ============================================================
|
||||
-- constellation_full_name: known abbreviations
|
||||
-- ============================================================
|
||||
SELECT constellation_full_name('Ari') AS aries;
|
||||
aries
|
||||
-------
|
||||
Aries
|
||||
(1 row)
|
||||
|
||||
SELECT constellation_full_name('CMa') AS canis_major;
|
||||
canis_major
|
||||
-------------
|
||||
Canis Major
|
||||
(1 row)
|
||||
|
||||
SELECT constellation_full_name('UMi') AS ursa_minor;
|
||||
ursa_minor
|
||||
------------
|
||||
Ursa Minor
|
||||
(1 row)
|
||||
|
||||
SELECT constellation_full_name('Ori') AS orion;
|
||||
orion
|
||||
-------
|
||||
Orion
|
||||
(1 row)
|
||||
|
||||
SELECT constellation_full_name('Cyg') AS cygnus;
|
||||
cygnus
|
||||
--------
|
||||
Cygnus
|
||||
(1 row)
|
||||
|
||||
SELECT constellation_full_name('Oct') AS octans;
|
||||
octans
|
||||
--------
|
||||
Octans
|
||||
(1 row)
|
||||
|
||||
SELECT constellation_full_name('TrA') AS tri_australe;
|
||||
tri_australe
|
||||
---------------------
|
||||
Triangulum Australe
|
||||
(1 row)
|
||||
|
||||
-- ============================================================
|
||||
-- constellation_full_name: composability with constellation()
|
||||
-- ============================================================
|
||||
-- Chain: equatorial -> abbreviation -> full name
|
||||
SELECT constellation_full_name(constellation(2.5303, 89.264)) AS polaris_full;
|
||||
polaris_full
|
||||
--------------
|
||||
Ursa Minor
|
||||
(1 row)
|
||||
|
||||
SELECT constellation_full_name(constellation(6.7525, -16.716)) AS sirius_full;
|
||||
sirius_full
|
||||
-------------
|
||||
Canis Major
|
||||
(1 row)
|
||||
|
||||
-- Chain with planet equatorial
|
||||
SELECT constellation_full_name(
|
||||
constellation(planet_equatorial(5, '2024-01-15 12:00:00+00'::timestamptz))
|
||||
) AS jupiter_full;
|
||||
jupiter_full
|
||||
--------------
|
||||
Aries
|
||||
(1 row)
|
||||
|
||||
-- ============================================================
|
||||
-- constellation_full_name: NULL for invalid abbreviation
|
||||
-- ============================================================
|
||||
SELECT constellation_full_name('XYZ') IS NULL AS invalid_returns_null;
|
||||
invalid_returns_null
|
||||
----------------------
|
||||
t
|
||||
(1 row)
|
||||
|
||||
SELECT constellation_full_name('') IS NULL AS empty_returns_null;
|
||||
empty_returns_null
|
||||
--------------------
|
||||
t
|
||||
(1 row)
|
||||
|
||||
SELECT constellation_full_name('Toolong') IS NULL AS toolong_returns_null;
|
||||
toolong_returns_null
|
||||
----------------------
|
||||
t
|
||||
(1 row)
|
||||
|
||||
-- ============================================================
|
||||
-- constellation_full_name: all 88 are reachable (count check)
|
||||
-- ============================================================
|
||||
-- Use generate_series to count distinct full names from the
|
||||
-- known constellation abbreviations via a spot check
|
||||
SELECT count(DISTINCT constellation_full_name(abbr)) = 7
|
||||
AS spot_check_7_names
|
||||
FROM (VALUES ('Ari'), ('CMa'), ('UMi'), ('Ori'), ('Cyg'), ('Oct'), ('TrA')) AS t(abbr);
|
||||
spot_check_7_names
|
||||
--------------------
|
||||
t
|
||||
(1 row)
|
||||
|
||||
-- ============================================================
|
||||
-- sun_rise_set_status: mid-latitude (Eagle, Idaho) in winter
|
||||
-- Sun rises and sets normally
|
||||
-- ============================================================
|
||||
SELECT sun_rise_set_status('(43.7,-116.4,800)'::observer, '2024-01-15 00:00:00+00'::timestamptz)
|
||||
AS sun_status_midlat;
|
||||
sun_status_midlat
|
||||
-------------------
|
||||
rises_and_sets
|
||||
(1 row)
|
||||
|
||||
-- ============================================================
|
||||
-- sun_rise_set_status: 70N in June (midnight sun)
|
||||
-- ============================================================
|
||||
SELECT sun_rise_set_status('(70.0,25.0,0)'::observer, '2024-06-21 00:00:00+00'::timestamptz)
|
||||
AS sun_status_midnight_sun;
|
||||
sun_status_midnight_sun
|
||||
-------------------------
|
||||
circumpolar
|
||||
(1 row)
|
||||
|
||||
-- ============================================================
|
||||
-- sun_rise_set_status: 70N in December (polar night)
|
||||
-- ============================================================
|
||||
SELECT sun_rise_set_status('(70.0,25.0,0)'::observer, '2024-12-21 00:00:00+00'::timestamptz)
|
||||
AS sun_status_polar_night;
|
||||
sun_status_polar_night
|
||||
------------------------
|
||||
never_rises
|
||||
(1 row)
|
||||
|
||||
-- ============================================================
|
||||
-- moon_rise_set_status: mid-latitude — Moon normally rises/sets
|
||||
-- ============================================================
|
||||
SELECT moon_rise_set_status('(43.7,-116.4,800)'::observer, '2024-01-15 00:00:00+00'::timestamptz)
|
||||
AS moon_status_midlat;
|
||||
moon_status_midlat
|
||||
--------------------
|
||||
rises_and_sets
|
||||
(1 row)
|
||||
|
||||
-- ============================================================
|
||||
-- planet_rise_set_status: Jupiter from mid-latitude (normal)
|
||||
-- ============================================================
|
||||
SELECT planet_rise_set_status(5, '(43.7,-116.4,800)'::observer, '2024-01-15 00:00:00+00'::timestamptz)
|
||||
AS jupiter_status_midlat;
|
||||
jupiter_status_midlat
|
||||
-----------------------
|
||||
rises_and_sets
|
||||
(1 row)
|
||||
|
||||
-- ============================================================
|
||||
-- Consistency: status matches rise/set NULL contract
|
||||
-- ============================================================
|
||||
-- When sun_next_set returns NULL (circumpolar), status should say so
|
||||
SELECT sun_next_set('(70.0,25.0,0)'::observer, '2024-06-21 00:00:00+00'::timestamptz) IS NULL
|
||||
AS sun_no_set_null;
|
||||
sun_no_set_null
|
||||
-----------------
|
||||
t
|
||||
(1 row)
|
||||
|
||||
SELECT sun_rise_set_status('(70.0,25.0,0)'::observer, '2024-06-21 00:00:00+00'::timestamptz)
|
||||
= 'circumpolar' AS status_confirms_circumpolar;
|
||||
status_confirms_circumpolar
|
||||
-----------------------------
|
||||
t
|
||||
(1 row)
|
||||
|
||||
-- When sun_next_rise returns NULL (polar night), status should say so
|
||||
SELECT sun_next_rise('(70.0,25.0,0)'::observer, '2024-12-21 00:00:00+00'::timestamptz) IS NULL
|
||||
AS sun_no_rise_null;
|
||||
sun_no_rise_null
|
||||
------------------
|
||||
t
|
||||
(1 row)
|
||||
|
||||
SELECT sun_rise_set_status('(70.0,25.0,0)'::observer, '2024-12-21 00:00:00+00'::timestamptz)
|
||||
= 'never_rises' AS status_confirms_never_rises;
|
||||
status_confirms_never_rises
|
||||
-----------------------------
|
||||
t
|
||||
(1 row)
|
||||
|
||||
-- ============================================================
|
||||
-- Error cases
|
||||
-- ============================================================
|
||||
-- Invalid body_id for planet_rise_set_status
|
||||
DO $$ BEGIN PERFORM planet_rise_set_status(0, '(43.7,-116.4,800)'::observer, '2024-01-15 00:00:00+00'::timestamptz); EXCEPTION WHEN OTHERS THEN RAISE NOTICE 'body_id=0: %', SQLERRM; END $$;
|
||||
NOTICE: body_id=0: planet_rise_set_status: body_id 0 must be 1-8 (Mercury-Neptune)
|
||||
DO $$ BEGIN PERFORM planet_rise_set_status(3, '(43.7,-116.4,800)'::observer, '2024-01-15 00:00:00+00'::timestamptz); EXCEPTION WHEN OTHERS THEN RAISE NOTICE 'body_id=3(Earth): %', SQLERRM; END $$;
|
||||
NOTICE: body_id=3(Earth): cannot observe Earth from Earth
|
||||
DO $$ BEGIN PERFORM planet_rise_set_status(9, '(43.7,-116.4,800)'::observer, '2024-01-15 00:00:00+00'::timestamptz); EXCEPTION WHEN OTHERS THEN RAISE NOTICE 'body_id=9: %', SQLERRM; END $$;
|
||||
NOTICE: body_id=9: planet_rise_set_status: body_id 9 must be 1-8 (Mercury-Neptune)
|
||||
109
test/sql/v015_features.sql
Normal file
109
test/sql/v015_features.sql
Normal file
@ -0,0 +1,109 @@
|
||||
-- v015_features.sql -- Tests for v0.15.0: constellation_full_name + rise_set_status
|
||||
--
|
||||
-- Verifies the constellation full name lookup and the rise/set
|
||||
-- status diagnostic functions.
|
||||
CREATE EXTENSION IF NOT EXISTS pg_orrery;
|
||||
|
||||
-- ============================================================
|
||||
-- constellation_full_name: known abbreviations
|
||||
-- ============================================================
|
||||
|
||||
SELECT constellation_full_name('Ari') AS aries;
|
||||
SELECT constellation_full_name('CMa') AS canis_major;
|
||||
SELECT constellation_full_name('UMi') AS ursa_minor;
|
||||
SELECT constellation_full_name('Ori') AS orion;
|
||||
SELECT constellation_full_name('Cyg') AS cygnus;
|
||||
SELECT constellation_full_name('Oct') AS octans;
|
||||
SELECT constellation_full_name('TrA') AS tri_australe;
|
||||
|
||||
-- ============================================================
|
||||
-- constellation_full_name: composability with constellation()
|
||||
-- ============================================================
|
||||
|
||||
-- Chain: equatorial -> abbreviation -> full name
|
||||
SELECT constellation_full_name(constellation(2.5303, 89.264)) AS polaris_full;
|
||||
SELECT constellation_full_name(constellation(6.7525, -16.716)) AS sirius_full;
|
||||
|
||||
-- Chain with planet equatorial
|
||||
SELECT constellation_full_name(
|
||||
constellation(planet_equatorial(5, '2024-01-15 12:00:00+00'::timestamptz))
|
||||
) AS jupiter_full;
|
||||
|
||||
-- ============================================================
|
||||
-- constellation_full_name: NULL for invalid abbreviation
|
||||
-- ============================================================
|
||||
|
||||
SELECT constellation_full_name('XYZ') IS NULL AS invalid_returns_null;
|
||||
SELECT constellation_full_name('') IS NULL AS empty_returns_null;
|
||||
SELECT constellation_full_name('Toolong') IS NULL AS toolong_returns_null;
|
||||
|
||||
-- ============================================================
|
||||
-- constellation_full_name: all 88 are reachable (count check)
|
||||
-- ============================================================
|
||||
|
||||
-- Use generate_series to count distinct full names from the
|
||||
-- known constellation abbreviations via a spot check
|
||||
SELECT count(DISTINCT constellation_full_name(abbr)) = 7
|
||||
AS spot_check_7_names
|
||||
FROM (VALUES ('Ari'), ('CMa'), ('UMi'), ('Ori'), ('Cyg'), ('Oct'), ('TrA')) AS t(abbr);
|
||||
|
||||
-- ============================================================
|
||||
-- sun_rise_set_status: mid-latitude (Eagle, Idaho) in winter
|
||||
-- Sun rises and sets normally
|
||||
-- ============================================================
|
||||
|
||||
SELECT sun_rise_set_status('(43.7,-116.4,800)'::observer, '2024-01-15 00:00:00+00'::timestamptz)
|
||||
AS sun_status_midlat;
|
||||
|
||||
-- ============================================================
|
||||
-- sun_rise_set_status: 70N in June (midnight sun)
|
||||
-- ============================================================
|
||||
|
||||
SELECT sun_rise_set_status('(70.0,25.0,0)'::observer, '2024-06-21 00:00:00+00'::timestamptz)
|
||||
AS sun_status_midnight_sun;
|
||||
|
||||
-- ============================================================
|
||||
-- sun_rise_set_status: 70N in December (polar night)
|
||||
-- ============================================================
|
||||
|
||||
SELECT sun_rise_set_status('(70.0,25.0,0)'::observer, '2024-12-21 00:00:00+00'::timestamptz)
|
||||
AS sun_status_polar_night;
|
||||
|
||||
-- ============================================================
|
||||
-- moon_rise_set_status: mid-latitude — Moon normally rises/sets
|
||||
-- ============================================================
|
||||
|
||||
SELECT moon_rise_set_status('(43.7,-116.4,800)'::observer, '2024-01-15 00:00:00+00'::timestamptz)
|
||||
AS moon_status_midlat;
|
||||
|
||||
-- ============================================================
|
||||
-- planet_rise_set_status: Jupiter from mid-latitude (normal)
|
||||
-- ============================================================
|
||||
|
||||
SELECT planet_rise_set_status(5, '(43.7,-116.4,800)'::observer, '2024-01-15 00:00:00+00'::timestamptz)
|
||||
AS jupiter_status_midlat;
|
||||
|
||||
-- ============================================================
|
||||
-- Consistency: status matches rise/set NULL contract
|
||||
-- ============================================================
|
||||
|
||||
-- When sun_next_set returns NULL (circumpolar), status should say so
|
||||
SELECT sun_next_set('(70.0,25.0,0)'::observer, '2024-06-21 00:00:00+00'::timestamptz) IS NULL
|
||||
AS sun_no_set_null;
|
||||
SELECT sun_rise_set_status('(70.0,25.0,0)'::observer, '2024-06-21 00:00:00+00'::timestamptz)
|
||||
= 'circumpolar' AS status_confirms_circumpolar;
|
||||
|
||||
-- When sun_next_rise returns NULL (polar night), status should say so
|
||||
SELECT sun_next_rise('(70.0,25.0,0)'::observer, '2024-12-21 00:00:00+00'::timestamptz) IS NULL
|
||||
AS sun_no_rise_null;
|
||||
SELECT sun_rise_set_status('(70.0,25.0,0)'::observer, '2024-12-21 00:00:00+00'::timestamptz)
|
||||
= 'never_rises' AS status_confirms_never_rises;
|
||||
|
||||
-- ============================================================
|
||||
-- Error cases
|
||||
-- ============================================================
|
||||
|
||||
-- Invalid body_id for planet_rise_set_status
|
||||
DO $$ BEGIN PERFORM planet_rise_set_status(0, '(43.7,-116.4,800)'::observer, '2024-01-15 00:00:00+00'::timestamptz); EXCEPTION WHEN OTHERS THEN RAISE NOTICE 'body_id=0: %', SQLERRM; END $$;
|
||||
DO $$ BEGIN PERFORM planet_rise_set_status(3, '(43.7,-116.4,800)'::observer, '2024-01-15 00:00:00+00'::timestamptz); EXCEPTION WHEN OTHERS THEN RAISE NOTICE 'body_id=3(Earth): %', SQLERRM; END $$;
|
||||
DO $$ BEGIN PERFORM planet_rise_set_status(9, '(43.7,-116.4,800)'::observer, '2024-01-15 00:00:00+00'::timestamptz); EXCEPTION WHEN OTHERS THEN RAISE NOTICE 'body_id=9: %', SQLERRM; END $$;
|
||||
Loading…
x
Reference in New Issue
Block a user