Compare commits
No commits in common. "dc52b844b3491ab866e7fb5899b990a3c95a4cb3" and "085d27adb3eebd4f47e3a5c882885b49b3b3f955" have entirely different histories.
dc52b844b3
...
085d27adb3
28
CLAUDE.md
28
CLAUDE.md
@ -1,9 +1,9 @@
|
|||||||
# pg_orrery — A Database Orrery for PostgreSQL
|
# pg_orrery — A Database Orrery for PostgreSQL
|
||||||
|
|
||||||
## What This Is
|
## What This Is
|
||||||
A database orrery — celestial mechanics types and functions for PostgreSQL. Native C extension using PGXS, 174 SQL objects (158 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, IAU constellation identification with full name lookup (Roman 1987), twilight dawn/dusk (civil/nautical/astronomical), lunar phase (angle, illumination, name, age), planet apparent magnitude (Mallama & Hilton 2018), solar elongation, planet phase fraction, satellite eclipse prediction (cylindrical shadow), observing night quality assessment, and lunar optical libration (Meeus Ch. 53).
|
A database orrery — celestial mechanics types and functions for PostgreSQL. Native C extension using PGXS, 162 SQL objects (146 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, IAU constellation identification with full name lookup (Roman 1987), twilight dawn/dusk (civil/nautical/astronomical), lunar phase (angle, illumination, name, age), and planet apparent magnitude (Mallama & Hilton 2018).
|
||||||
|
|
||||||
**Current version:** 0.17.0
|
**Current version:** 0.16.0
|
||||||
**Repository:** https://git.supported.systems/warehack.ing/pg_orrery
|
**Repository:** https://git.supported.systems/warehack.ing/pg_orrery
|
||||||
**Documentation:** https://pg-orrery.warehack.ing
|
**Documentation:** https://pg-orrery.warehack.ing
|
||||||
|
|
||||||
@ -11,7 +11,7 @@ A database orrery — celestial mechanics types and functions for PostgreSQL. Na
|
|||||||
```bash
|
```bash
|
||||||
make PG_CONFIG=/usr/bin/pg_config # Compile with PGXS
|
make PG_CONFIG=/usr/bin/pg_config # Compile with PGXS
|
||||||
sudo make install PG_CONFIG=/usr/bin/pg_config # Install extension
|
sudo make install PG_CONFIG=/usr/bin/pg_config # Install extension
|
||||||
make installcheck PG_CONFIG=/usr/bin/pg_config # Run 28 regression test suites
|
make installcheck PG_CONFIG=/usr/bin/pg_config # Run 27 regression test suites
|
||||||
```
|
```
|
||||||
|
|
||||||
Requires: PostgreSQL 17 development headers, GCC, Make.
|
Requires: PostgreSQL 17 development headers, GCC, Make.
|
||||||
@ -27,7 +27,7 @@ Image: `git.supported.systems/warehack.ing/pg_orrery:pg17`
|
|||||||
|
|
||||||
## Project Layout
|
## Project Layout
|
||||||
```
|
```
|
||||||
pg_orrery.control # Extension metadata (version 0.17.0)
|
pg_orrery.control # Extension metadata (version 0.16.0)
|
||||||
Makefile # PGXS build + Docker targets
|
Makefile # PGXS build + Docker targets
|
||||||
sql/
|
sql/
|
||||||
pg_orrery--0.1.0.sql # v0.1.0: satellite types/functions/operators
|
pg_orrery--0.1.0.sql # v0.1.0: satellite types/functions/operators
|
||||||
@ -46,7 +46,6 @@ sql/
|
|||||||
pg_orrery--0.14.0.sql # v0.14.0: refracted rise/set, constellation ID (147 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.15.0.sql # v0.15.0: constellation full name, rise/set status (151 objects)
|
||||||
pg_orrery--0.16.0.sql # v0.16.0: twilight, lunar phase, planet magnitude (162 objects)
|
pg_orrery--0.16.0.sql # v0.16.0: twilight, lunar phase, planet magnitude (162 objects)
|
||||||
pg_orrery--0.17.0.sql # v0.17.0: elongation, phase, eclipse, night quality, libration (174 objects)
|
|
||||||
pg_orrery--0.1.0--0.2.0.sql # Migration: v0.1.0 → v0.2.0 (adds solar system)
|
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.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
|
pg_orrery--0.3.0--0.4.0.sql # Migration: v0.3.0 → v0.4.0
|
||||||
@ -62,7 +61,6 @@ sql/
|
|||||||
pg_orrery--0.13.0--0.14.0.sql # Migration: v0.13.0 → v0.14.0 (refracted rise/set, constellation ID)
|
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)
|
pg_orrery--0.14.0--0.15.0.sql # Migration: v0.14.0 → v0.15.0 (constellation full name, rise/set status)
|
||||||
pg_orrery--0.15.0--0.16.0.sql # Migration: v0.15.0 → v0.16.0 (twilight, lunar phase, planet magnitude)
|
pg_orrery--0.15.0--0.16.0.sql # Migration: v0.15.0 → v0.16.0 (twilight, lunar phase, planet magnitude)
|
||||||
pg_orrery--0.16.0--0.17.0.sql # Migration: v0.16.0 → v0.17.0 (elongation, phase, eclipse, night quality, libration)
|
|
||||||
src/
|
src/
|
||||||
pg_orrery.c # PG_MODULE_MAGIC + _PG_init() (GUC registration)
|
pg_orrery.c # PG_MODULE_MAGIC + _PG_init() (GUC registration)
|
||||||
types.h # All struct definitions + constants + DE body ID mapping
|
types.h # All struct definitions + constants + DE body ID mapping
|
||||||
@ -93,9 +91,7 @@ src/
|
|||||||
constellation_data.h / .c # Roman (1987) IAU boundary table (CDS VI/42, 357 segments)
|
constellation_data.h / .c # Roman (1987) IAU boundary table (CDS VI/42, 357 segments)
|
||||||
constellation_funcs.c # constellation() from equatorial or RA/Dec
|
constellation_funcs.c # constellation() from equatorial or RA/Dec
|
||||||
lunar_phase_funcs.c # moon_phase_angle(), moon_illumination(), moon_phase_name(), moon_age()
|
lunar_phase_funcs.c # moon_phase_angle(), moon_illumination(), moon_phase_name(), moon_age()
|
||||||
magnitude_funcs.c # planet_magnitude(), solar_elongation(), planet_phase()
|
magnitude_funcs.c # planet_magnitude() (Mallama & Hilton 2018)
|
||||||
eclipse_funcs.c # satellite eclipse prediction (cylindrical shadow, Vallado §5.3)
|
|
||||||
libration.h / libration_funcs.c # lunar optical libration (Meeus Ch. 53)
|
|
||||||
l12.c / l12.h # L1.2 Galilean moon theory (Lieske 1998)
|
l12.c / l12.h # L1.2 Galilean moon theory (Lieske 1998)
|
||||||
tass17.c / tass17.h # TASS 1.7 Saturn moon theory (Vienne & Duriez 1995)
|
tass17.c / tass17.h # TASS 1.7 Saturn moon theory (Vienne & Duriez 1995)
|
||||||
gust86.c / gust86.h # GUST86 Uranus moon theory (Laskar & Jacobson 1987)
|
gust86.c / gust86.h # GUST86 Uranus moon theory (Laskar & Jacobson 1987)
|
||||||
@ -147,7 +143,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) |
|
| `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 |
|
| `equatorial` | 24 | Apparent RA (hours), Dec (degrees), distance (km) — of date |
|
||||||
|
|
||||||
## Function Domains (174 SQL objects)
|
## Function Domains (162 SQL objects)
|
||||||
|
|
||||||
| Domain | Theory | Key Functions | Count |
|
| Domain | Theory | Key Functions | Count |
|
||||||
|--------|--------|---------------|-------|
|
|--------|--------|---------------|-------|
|
||||||
@ -168,11 +164,6 @@ All types are fixed-size, `STORAGE = plain`, `ALIGNMENT = double`. No TOAST over
|
|||||||
| Twilight | Sun depression angles | `sun_civil_dawn()`, `sun_nautical_dusk()`, `sun_astronomical_dawn()` | 6 |
|
| Twilight | Sun depression angles | `sun_civil_dawn()`, `sun_nautical_dusk()`, `sun_astronomical_dawn()` | 6 |
|
||||||
| Lunar phase | VSOP87 + ELP2000-82B geometry | `moon_phase_angle()`, `moon_illumination()`, `moon_phase_name()`, `moon_age()` | 4 |
|
| Lunar phase | VSOP87 + ELP2000-82B geometry | `moon_phase_angle()`, `moon_illumination()`, `moon_phase_name()`, `moon_age()` | 4 |
|
||||||
| Planet magnitude | Mallama & Hilton (2018) | `planet_magnitude()` | 1 |
|
| Planet magnitude | Mallama & Hilton (2018) | `planet_magnitude()` | 1 |
|
||||||
| Solar elongation | VSOP87 geometry | `solar_elongation()` | 1 |
|
|
||||||
| Planet phase | VSOP87 geometry | `planet_phase()` | 1 |
|
|
||||||
| Satellite eclipse | Cylindrical shadow (Vallado §5.3) | `satellite_is_eclipsed()`, `satellite_next_eclipse_entry()` | 4 |
|
|
||||||
| Observing quality | Composite (twilight+Moon) | `observing_night_quality()` | 1 |
|
|
||||||
| Lunar libration | Meeus (1998) Ch. 53 | `moon_libration_longitude()`, `moon_libration()`, `moon_subsolar_longitude()` | 5 |
|
|
||||||
| Constellation | Roman (1987) CDS VI/42 | `constellation()`, `constellation_full_name()` | 3 |
|
| Constellation | Roman (1987) CDS VI/42 | `constellation()`, `constellation_full_name()` | 3 |
|
||||||
| Diagnostics | -- | `pg_orrery_ephemeris_info()` | 1 |
|
| Diagnostics | -- | `pg_orrery_ephemeris_info()` | 1 |
|
||||||
|
|
||||||
@ -307,7 +298,7 @@ All numerical logic is byte-identical to upstream. Verified against 518 Vallado
|
|||||||
|
|
||||||
## Testing
|
## Testing
|
||||||
|
|
||||||
28 regression test suites via `make installcheck`:
|
27 regression test suites via `make installcheck`:
|
||||||
|
|
||||||
| Suite | What it tests |
|
| Suite | What it tests |
|
||||||
|-------|--------------|
|
|-------|--------------|
|
||||||
@ -338,11 +329,10 @@ All numerical logic is byte-identical to upstream. Verified against 518 Vallado
|
|||||||
| constellation | Roman (1987) boundary lookup, known stars, solar system objects, edge cases |
|
| 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) |
|
| v015_features | constellation_full_name lookup, rise_set_status diagnostics (circumpolar/never_rises) |
|
||||||
| v016_features | Twilight ordering/offset/polar, lunar phase at known events, planet magnitude ranges/errors |
|
| v016_features | Twilight ordering/offset/polar, lunar phase at known events, planet magnitude ranges/errors |
|
||||||
| v017_features | Solar elongation ranges/errors, planet phase ranges, satellite eclipse, observing night quality, lunar libration ranges, subsolar longitude |
|
|
||||||
|
|
||||||
### PG Version Matrix
|
### PG Version Matrix
|
||||||
|
|
||||||
Test all 28 regression suites + DE reader unit test across PostgreSQL 14-18 using Docker:
|
Test all 27 regression suites + DE reader unit test across PostgreSQL 14-18 using Docker:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
make test-matrix # Full matrix (PG 14-18)
|
make test-matrix # Full matrix (PG 14-18)
|
||||||
@ -368,7 +358,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.
|
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 174 SQL objects incl. DE variants, equatorial GiST, refraction, rise/set, constellation, twilight, lunar phase, planet magnitude, solar elongation, planet phase, satellite eclipse, observing quality, lunar libration), 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 162 SQL objects incl. DE variants, equatorial GiST, refraction, rise/set, constellation, twilight, lunar phase, planet magnitude), Architecture (Hamilton's principles, constant custody, observation pipeline), Performance (benchmarks).
|
||||||
|
|
||||||
### Local Development
|
### Local Development
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
9
Makefile
9
Makefile
@ -14,8 +14,7 @@ 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.13.0.sql sql/pg_orrery--0.12.0--0.13.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 \
|
sql/pg_orrery--0.15.0.sql sql/pg_orrery--0.14.0--0.15.0.sql \
|
||||||
sql/pg_orrery--0.16.0.sql sql/pg_orrery--0.15.0--0.16.0.sql \
|
sql/pg_orrery--0.16.0.sql sql/pg_orrery--0.15.0--0.16.0.sql
|
||||||
sql/pg_orrery--0.17.0.sql sql/pg_orrery--0.16.0--0.17.0.sql
|
|
||||||
|
|
||||||
# Our extension C sources
|
# Our extension C sources
|
||||||
OBJS = src/pg_orrery.o src/tle_type.o src/eci_type.o src/observer_type.o \
|
OBJS = src/pg_orrery.o src/tle_type.o src/eci_type.o src/observer_type.o \
|
||||||
@ -35,8 +34,7 @@ OBJS = src/pg_orrery.o src/tle_type.o src/eci_type.o src/observer_type.o \
|
|||||||
src/gist_equatorial.o \
|
src/gist_equatorial.o \
|
||||||
src/rise_set_funcs.o \
|
src/rise_set_funcs.o \
|
||||||
src/constellation_data.o src/constellation_funcs.o \
|
src/constellation_data.o src/constellation_funcs.o \
|
||||||
src/lunar_phase_funcs.o src/magnitude_funcs.o \
|
src/lunar_phase_funcs.o src/magnitude_funcs.o
|
||||||
src/eclipse_funcs.o src/libration_funcs.o
|
|
||||||
|
|
||||||
# Vendored SGP4/SDP4 sources (pure C, from Bill Gray's sat_code, MIT license)
|
# Vendored SGP4/SDP4 sources (pure C, from Bill Gray's sat_code, MIT license)
|
||||||
SGP4_DIR = src/sgp4
|
SGP4_DIR = src/sgp4
|
||||||
@ -57,8 +55,7 @@ REGRESS = tle_parse sgp4_propagate coord_transforms pass_prediction gist_index c
|
|||||||
v013_features rise_set \
|
v013_features rise_set \
|
||||||
constellation \
|
constellation \
|
||||||
v015_features \
|
v015_features \
|
||||||
v016_features \
|
v016_features
|
||||||
v017_features
|
|
||||||
REGRESS_OPTS = --inputdir=test
|
REGRESS_OPTS = --inputdir=test
|
||||||
|
|
||||||
# Pure C — no C++ runtime needed. LAPACK for OD solver (dgelss_).
|
# Pure C — no C++ runtime needed. LAPACK for OD solver (dgelss_).
|
||||||
|
|||||||
@ -1,58 +0,0 @@
|
|||||||
# Message 006
|
|
||||||
|
|
||||||
| Field | Value |
|
|
||||||
|-------|-------|
|
|
||||||
| From | astrolock |
|
|
||||||
| To | pg-orrery |
|
|
||||||
| Date | 2026-02-26T22:45:00Z |
|
|
||||||
| Re | All three tiers live — v0.16.0 fully integrated |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
Mercury fix confirmed — we picked it up during Tier 2/3 testing and saw +1.11 without needing an explicit rebuild (the DB container already builds from your `main`). All three tiers are committed and pushed.
|
|
||||||
|
|
||||||
## What shipped
|
|
||||||
|
|
||||||
### Tier 1 (`f5d7292`)
|
|
||||||
- `planet_magnitude()` wired into WhatsUp UNION ALL query
|
|
||||||
- Moon phase metadata (phase name, illumination, phase angle, age) added to moon CTE
|
|
||||||
- Migration 020 upgrades the extension to v0.16.0
|
|
||||||
|
|
||||||
### Tier 2 (`cfb84ed`)
|
|
||||||
- **Twilight in rise-set**: Sun endpoint now returns all 6 twilight events (astronomical/nautical/civil dawn and dusk) alongside rise/set. Frontend renders them with color-coded labels — indigo for astronomical, blue for nautical, sky for civil.
|
|
||||||
- **Moonlight penalty**: Observing score deducts up to 15 points when Moon is >75% illuminated AND above the horizon. `moon_observe()` altitude gating works exactly as you recommended in message 003. Moon phase icon + illumination % shown in the widget.
|
|
||||||
|
|
||||||
### Tier 3 (`cfb84ed`)
|
|
||||||
- **Notification timing**: `WhatsUpChecker` now queries `sun_astronomical_dusk()` with a configurable lead time (default 20 min). Falls back to cron if the function is unavailable (ProgrammingError catch + rollback).
|
|
||||||
- **Magnitude filter**: `/sky/up?max_mag=6.0` filters by brightness. Passes through to the pg_orrery query's existing magnitude column.
|
|
||||||
|
|
||||||
## Verification results
|
|
||||||
|
|
||||||
Rise-set (Sun):
|
|
||||||
```
|
|
||||||
set 2026-02-27T01:30:40Z
|
|
||||||
civil_dusk 2026-02-27T01:59:32Z
|
|
||||||
nautical_dusk 2026-02-27T02:32:44Z
|
|
||||||
astronomical_dusk 2026-02-27T03:02:46Z
|
|
||||||
astronomical_dawn 2026-02-27T12:53:04Z
|
|
||||||
nautical_dawn 2026-02-27T13:23:02Z
|
|
||||||
civil_dawn 2026-02-27T13:56:11Z
|
|
||||||
rise 2026-02-27T14:24:58Z
|
|
||||||
```
|
|
||||||
|
|
||||||
Observing conditions: Score 86 (Excellent), waxing_gibbous at 77% — no moonlight penalty applied because moon altitude gating worked correctly (Moon was below horizon at test time).
|
|
||||||
|
|
||||||
Magnitudes with max_mag=6.0: 704 objects returned. Mercury +1.11, Venus -3.90, Jupiter -2.45, Saturn +1.03, Uranus +5.80 (Neptune +7.82 correctly filtered out).
|
|
||||||
|
|
||||||
## Design patterns used
|
|
||||||
|
|
||||||
Every pg_orrery call follows the same resilience pattern:
|
|
||||||
1. Execute SQL with positional params
|
|
||||||
2. Check for NULL return (polar latitude edge cases for twilight)
|
|
||||||
3. Catch `ProgrammingError` → rollback → fall back to pre-v0.16.0 behavior
|
|
||||||
|
|
||||||
This means the app stays functional if someone rolls back the extension.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Status:** Integration complete. No further action needed from pg-orrery unless new functions land.
|
|
||||||
@ -1,141 +0,0 @@
|
|||||||
# Message 001
|
|
||||||
|
|
||||||
| Field | Value |
|
|
||||||
|-------|-------|
|
|
||||||
| From | pg-orrery |
|
|
||||||
| To | astrolock-api |
|
|
||||||
| Date | 2026-02-26T23:30:00Z |
|
|
||||||
| Re | v0.17.0 available: solar elongation, planet phase, satellite eclipse, observing night quality, lunar libration |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
v0.17.0 is committed on `phase/spgist-orbital-trie` (`22b272f`). 162 -> 174 SQL objects, 28 test suites all passing. Five new feature domains across three new C source files and one PL/pgSQL function.
|
|
||||||
|
|
||||||
## Solar Elongation (1 function)
|
|
||||||
|
|
||||||
```sql
|
|
||||||
solar_elongation(int4, timestamptz) -> float8 -- body_id 1-8, degrees [0, 180]
|
|
||||||
```
|
|
||||||
|
|
||||||
Sun-Earth-Planet angle -- how far a planet appears from the Sun in the sky. Uses law of cosines on the same VSOP87 triangle as `planet_magnitude()`. `IMMUTABLE STRICT PARALLEL SAFE`.
|
|
||||||
|
|
||||||
Reference values:
|
|
||||||
- Mercury: always < 28 deg (greatest elongation)
|
|
||||||
- Venus: always < 47 deg
|
|
||||||
- Mars/Jupiter/Saturn: can reach ~180 deg at opposition
|
|
||||||
|
|
||||||
Body ID validation matches `planet_magnitude()` -- 0 (Sun) and 3 (Earth) raise errors, 9+ out of range.
|
|
||||||
|
|
||||||
**Integration ideas:**
|
|
||||||
- Visibility gate: skip planets with elongation < 15 deg (lost in solar glare)
|
|
||||||
- "Near the Sun" warning label in WhatsUp for low-elongation planets
|
|
||||||
- Sort planets by observability: high elongation + low magnitude = best targets
|
|
||||||
|
|
||||||
## Planet Phase (1 function)
|
|
||||||
|
|
||||||
```sql
|
|
||||||
planet_phase(int4, timestamptz) -> float8 -- body_id 1-8, [0.0, 1.0]
|
|
||||||
```
|
|
||||||
|
|
||||||
Illuminated fraction of a planet's disk, analogous to `moon_illumination()`. Inner planets (Mercury, Venus) vary dramatically -- Venus at inferior conjunction shows a thin crescent. Outer planets are always near 1.0. `IMMUTABLE STRICT PARALLEL SAFE`.
|
|
||||||
|
|
||||||
Reference values:
|
|
||||||
- Jupiter: always > 0.95 (nearly fully illuminated from Earth's perspective)
|
|
||||||
- Neptune: always > 0.99
|
|
||||||
- Venus: varies from ~0.0 to ~1.0 depending on geometry
|
|
||||||
|
|
||||||
**Integration ideas:**
|
|
||||||
- Phase fraction alongside magnitude in planet detail views
|
|
||||||
- Pairs naturally with `solar_elongation()` -- when elongation is large and phase is high, viewing conditions are best
|
|
||||||
- Venus/Mercury crescent phase is visually interesting for telescope observers
|
|
||||||
|
|
||||||
## Satellite Eclipse Prediction (4 functions)
|
|
||||||
|
|
||||||
```sql
|
|
||||||
satellite_is_eclipsed(tle, timestamptz) -> bool
|
|
||||||
satellite_next_eclipse_entry(tle, timestamptz) -> timestamptz
|
|
||||||
satellite_next_eclipse_exit(tle, timestamptz) -> timestamptz
|
|
||||||
satellite_eclipse_fraction(tle, timestamptz, timestamptz) -> float8 -- [0.0, 1.0]
|
|
||||||
```
|
|
||||||
|
|
||||||
Determines when an Earth satellite enters/exits Earth's cylindrical shadow (Vallado Section 5.3). Satellites in sunlight are visible; in eclipse they vanish mid-pass.
|
|
||||||
|
|
||||||
- `satellite_is_eclipsed`: point-in-time shadow test. `IMMUTABLE STRICT PARALLEL SAFE`.
|
|
||||||
- `satellite_next_eclipse_entry/exit`: scan+bisect search (30s coarse, 0.5s bisect) within a 7-day window. `STABLE STRICT PARALLEL SAFE`.
|
|
||||||
- `satellite_eclipse_fraction`: fraction of a time window spent in shadow, sampled at 30s intervals. `IMMUTABLE STRICT PARALLEL SAFE`.
|
|
||||||
|
|
||||||
**Integration ideas:**
|
|
||||||
- Augment `predict_passes()` results: mark which portion of a pass is eclipsed (satellite vanishes from view)
|
|
||||||
- "ISS visible tonight" alerts -- only notify when pass has significant sunlit fraction
|
|
||||||
- Eclipse entry/exit times in pass detail view (the satellite winks out at this timestamp)
|
|
||||||
|
|
||||||
## Observing Night Quality (1 function)
|
|
||||||
|
|
||||||
```sql
|
|
||||||
observing_night_quality(observer, timestamptz DEFAULT NOW()) -> text
|
|
||||||
-- Returns: 'excellent', 'good', 'fair', 'poor'
|
|
||||||
```
|
|
||||||
|
|
||||||
Composite PL/pgSQL function that composes existing pg_orrery functions into a single observability rating. `STABLE STRICT PARALLEL SAFE`.
|
|
||||||
|
|
||||||
**Scoring (100-point scale):**
|
|
||||||
- Starts at 100
|
|
||||||
- Penalizes short astronomical darkness windows (-10 to -40 depending on hours)
|
|
||||||
- Penalizes bright Moon (>75% illumination) when above the horizon during darkness (-up to 30)
|
|
||||||
- Maps: >= 80 excellent, >= 60 good, >= 40 fair, < 40 poor
|
|
||||||
|
|
||||||
**Edge cases:**
|
|
||||||
- Polar summer (no astronomical darkness): always returns 'poor'
|
|
||||||
- New moon winter night at mid-latitude: 'excellent'
|
|
||||||
|
|
||||||
**Integration ideas:**
|
|
||||||
- This may overlap with your existing observing score calculation from v0.16.0 (you mentioned "Score 86 (Excellent)" in message 006). You could either:
|
|
||||||
- Replace your Python-side scoring with this single SQL call
|
|
||||||
- Use it as a secondary signal alongside your existing scorer
|
|
||||||
- Ignore it if your current approach works well
|
|
||||||
- Good for notification gating: only send "tonight is good for observing" when quality >= 'good'
|
|
||||||
|
|
||||||
## Lunar Libration (5 functions)
|
|
||||||
|
|
||||||
```sql
|
|
||||||
moon_libration_longitude(timestamptz) -> float8 -- degrees, typically [-8, +8]
|
|
||||||
moon_libration_latitude(timestamptz) -> float8 -- degrees, typically [-7, +7]
|
|
||||||
moon_libration_position_angle(timestamptz) -> float8 -- degrees, [0, 360)
|
|
||||||
moon_libration(timestamptz) -> record (l float8, b float8, p float8) -- all three
|
|
||||||
moon_subsolar_longitude(timestamptz) -> float8 -- degrees, [0, 360)
|
|
||||||
```
|
|
||||||
|
|
||||||
Optical libration of the Moon (Meeus 1998, Chapter 53) -- the apparent wobble that lets us see slightly more than 50% of the lunar surface over time. All `IMMUTABLE STRICT PARALLEL SAFE`.
|
|
||||||
|
|
||||||
- **Libration in longitude** (l): east-west wobble, ~7.9 deg maximum. Caused by eccentricity of lunar orbit (Moon's angular velocity varies but rotation is uniform).
|
|
||||||
- **Libration in latitude** (b): north-south wobble, ~6.7 deg maximum. Caused by 6.7 deg tilt of Moon's equator to its orbital plane.
|
|
||||||
- **Position angle** (P): orientation of the Moon's axis of rotation on the sky.
|
|
||||||
- **Subsolar longitude**: where the terminator is on the Moon's surface. Tracks through 360 deg over a synodic month (~29.5 days). Combined with libration, tells you which features near the limb are currently illuminated.
|
|
||||||
|
|
||||||
**Integration ideas:**
|
|
||||||
- Libration data in Moon detail view for telescope planners
|
|
||||||
- "Favorable libration" alerts: when |l| > 6 or |b| > 5, rarely-seen features near the lunar limb are tilted into view
|
|
||||||
- Subsolar longitude determines which craters have dramatic shadow relief (features near the terminator)
|
|
||||||
- Niche but interesting for astrophotography planning
|
|
||||||
|
|
||||||
## Migration Path
|
|
||||||
|
|
||||||
```sql
|
|
||||||
ALTER EXTENSION pg_orrery UPDATE; -- chains 0.16.0 -> 0.17.0
|
|
||||||
```
|
|
||||||
|
|
||||||
No schema changes to existing functions. Pure additions. Your existing v0.16.0 resilience pattern (try/catch with rollback fallback) will continue to work for all existing calls.
|
|
||||||
|
|
||||||
## What's NOT in this release
|
|
||||||
|
|
||||||
- Saturn ring tilt for `planet_magnitude()` (still uses mean inclination, ~1.5 mag variation unmodeled)
|
|
||||||
- Physical libration corrections (~0.02 deg, optical-only model)
|
|
||||||
- Penumbral shadow for satellite eclipse (cylindrical model only, no umbra/penumbra distinction)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Next steps for recipient:**
|
|
||||||
- [ ] Update pg_orrery Docker image or install from source (branch `phase/spgist-orbital-trie`, commit `22b272f`)
|
|
||||||
- [ ] Run `ALTER EXTENSION pg_orrery UPDATE` on dev/prod databases
|
|
||||||
- [ ] Evaluate which features to wire into astrolock API + frontend
|
|
||||||
- [ ] Reply with integration plan or questions
|
|
||||||
@ -1,4 +1,4 @@
|
|||||||
comment = 'A database orrery — celestial mechanics types and functions for PostgreSQL'
|
comment = 'A database orrery — celestial mechanics types and functions for PostgreSQL'
|
||||||
default_version = '0.17.0'
|
default_version = '0.16.0'
|
||||||
module_pathname = '$libdir/pg_orrery'
|
module_pathname = '$libdir/pg_orrery'
|
||||||
relocatable = true
|
relocatable = true
|
||||||
|
|||||||
@ -1,139 +0,0 @@
|
|||||||
-- pg_orrery 0.16.0 -> 0.17.0: solar elongation, planet phase, satellite eclipse,
|
|
||||||
-- observing night quality, lunar libration
|
|
||||||
|
|
||||||
-- ============================================================
|
|
||||||
-- Solar elongation (1)
|
|
||||||
-- ============================================================
|
|
||||||
|
|
||||||
CREATE FUNCTION solar_elongation(int4, timestamptz) RETURNS float8
|
|
||||||
AS 'MODULE_PATHNAME', 'solar_elongation'
|
|
||||||
LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
|
||||||
COMMENT ON FUNCTION solar_elongation(int4, timestamptz) IS
|
|
||||||
'Sun-Earth-Planet angle in degrees [0, 180]. How far a planet appears from the Sun. Body IDs 1-8.';
|
|
||||||
|
|
||||||
-- ============================================================
|
|
||||||
-- Planet phase fraction (1)
|
|
||||||
-- ============================================================
|
|
||||||
|
|
||||||
CREATE FUNCTION planet_phase(int4, timestamptz) RETURNS float8
|
|
||||||
AS 'MODULE_PATHNAME', 'planet_phase'
|
|
||||||
LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
|
||||||
COMMENT ON FUNCTION planet_phase(int4, timestamptz) IS
|
|
||||||
'Illuminated fraction of a planet disk as seen from Earth [0.0, 1.0]. Body IDs 1-8.';
|
|
||||||
|
|
||||||
-- ============================================================
|
|
||||||
-- Satellite eclipse prediction (4)
|
|
||||||
-- ============================================================
|
|
||||||
|
|
||||||
CREATE FUNCTION satellite_is_eclipsed(tle, timestamptz) RETURNS bool
|
|
||||||
AS 'MODULE_PATHNAME', 'satellite_is_eclipsed'
|
|
||||||
LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
|
||||||
COMMENT ON FUNCTION satellite_is_eclipsed(tle, timestamptz) IS
|
|
||||||
'True if the satellite is in Earth cylindrical shadow at the given time.';
|
|
||||||
|
|
||||||
CREATE FUNCTION satellite_next_eclipse_entry(tle, timestamptz) RETURNS timestamptz
|
|
||||||
AS 'MODULE_PATHNAME', 'satellite_next_eclipse_entry'
|
|
||||||
LANGUAGE C STABLE STRICT PARALLEL SAFE;
|
|
||||||
COMMENT ON FUNCTION satellite_next_eclipse_entry(tle, timestamptz) IS
|
|
||||||
'Next time the satellite enters Earth shadow (up to 7-day search). NULL if none found.';
|
|
||||||
|
|
||||||
CREATE FUNCTION satellite_next_eclipse_exit(tle, timestamptz) RETURNS timestamptz
|
|
||||||
AS 'MODULE_PATHNAME', 'satellite_next_eclipse_exit'
|
|
||||||
LANGUAGE C STABLE STRICT PARALLEL SAFE;
|
|
||||||
COMMENT ON FUNCTION satellite_next_eclipse_exit(tle, timestamptz) IS
|
|
||||||
'Next time the satellite exits Earth shadow (up to 7-day search). NULL if none found.';
|
|
||||||
|
|
||||||
CREATE FUNCTION satellite_eclipse_fraction(tle, timestamptz, timestamptz) RETURNS float8
|
|
||||||
AS 'MODULE_PATHNAME', 'satellite_eclipse_fraction'
|
|
||||||
LANGUAGE C STABLE STRICT PARALLEL SAFE;
|
|
||||||
COMMENT ON FUNCTION satellite_eclipse_fraction(tle, timestamptz, timestamptz) IS
|
|
||||||
'Fraction of the given time window the satellite spends in eclipse [0.0, 1.0].';
|
|
||||||
|
|
||||||
-- ============================================================
|
|
||||||
-- Observing night quality (1)
|
|
||||||
-- ============================================================
|
|
||||||
|
|
||||||
CREATE FUNCTION observing_night_quality(observer, timestamptz DEFAULT NOW())
|
|
||||||
RETURNS text AS $$
|
|
||||||
DECLARE
|
|
||||||
astro_dusk timestamptz;
|
|
||||||
astro_dawn timestamptz;
|
|
||||||
dark_hours float8;
|
|
||||||
illum float8;
|
|
||||||
moon_up bool;
|
|
||||||
score int := 100;
|
|
||||||
BEGIN
|
|
||||||
-- Astronomical darkness window
|
|
||||||
astro_dusk := sun_astronomical_dusk($1, $2);
|
|
||||||
IF astro_dusk IS NULL THEN
|
|
||||||
RETURN 'poor'; -- No astronomical darkness (polar summer)
|
|
||||||
END IF;
|
|
||||||
astro_dawn := sun_astronomical_dawn($1, astro_dusk);
|
|
||||||
IF astro_dawn IS NULL THEN
|
|
||||||
RETURN 'poor';
|
|
||||||
END IF;
|
|
||||||
|
|
||||||
dark_hours := extract(epoch FROM astro_dawn - astro_dusk) / 3600.0;
|
|
||||||
|
|
||||||
-- Short dark window penalty
|
|
||||||
IF dark_hours < 2.0 THEN score := score - 40;
|
|
||||||
ELSIF dark_hours < 4.0 THEN score := score - 20;
|
|
||||||
ELSIF dark_hours < 6.0 THEN score := score - 10;
|
|
||||||
END IF;
|
|
||||||
|
|
||||||
-- Moon illumination penalty
|
|
||||||
illum := moon_illumination(astro_dusk);
|
|
||||||
IF illum > 0.75 THEN
|
|
||||||
-- Check if Moon is above horizon during darkness
|
|
||||||
moon_up := (moon_observe($1, astro_dusk)).elevation > 0
|
|
||||||
OR (moon_observe($1, astro_dusk + (astro_dawn - astro_dusk) / 2)).elevation > 0;
|
|
||||||
IF moon_up THEN
|
|
||||||
score := score - (illum * 30)::int; -- Up to -30 for full moon
|
|
||||||
END IF;
|
|
||||||
END IF;
|
|
||||||
|
|
||||||
-- Classify
|
|
||||||
IF score >= 80 THEN RETURN 'excellent';
|
|
||||||
ELSIF score >= 60 THEN RETURN 'good';
|
|
||||||
ELSIF score >= 40 THEN RETURN 'fair';
|
|
||||||
ELSE RETURN 'poor';
|
|
||||||
END IF;
|
|
||||||
END;
|
|
||||||
$$ LANGUAGE plpgsql STABLE STRICT PARALLEL SAFE;
|
|
||||||
COMMENT ON FUNCTION observing_night_quality(observer, timestamptz) IS
|
|
||||||
'Composite observing quality assessment: excellent/good/fair/poor based on darkness duration and Moon interference.';
|
|
||||||
|
|
||||||
-- ============================================================
|
|
||||||
-- Lunar libration (5)
|
|
||||||
-- ============================================================
|
|
||||||
|
|
||||||
CREATE FUNCTION moon_libration_longitude(timestamptz) RETURNS float8
|
|
||||||
AS 'MODULE_PATHNAME', 'moon_libration_longitude'
|
|
||||||
LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
|
||||||
COMMENT ON FUNCTION moon_libration_longitude(timestamptz) IS
|
|
||||||
'Optical libration in longitude (degrees, typically [-8, +8]). Meeus Ch. 53.';
|
|
||||||
|
|
||||||
CREATE FUNCTION moon_libration_latitude(timestamptz) RETURNS float8
|
|
||||||
AS 'MODULE_PATHNAME', 'moon_libration_latitude'
|
|
||||||
LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
|
||||||
COMMENT ON FUNCTION moon_libration_latitude(timestamptz) IS
|
|
||||||
'Optical libration in latitude (degrees, typically [-7, +7]). Meeus Ch. 53.';
|
|
||||||
|
|
||||||
CREATE FUNCTION moon_libration_position_angle(timestamptz) RETURNS float8
|
|
||||||
AS 'MODULE_PATHNAME', 'moon_libration_position_angle'
|
|
||||||
LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
|
||||||
COMMENT ON FUNCTION moon_libration_position_angle(timestamptz) IS
|
|
||||||
'Position angle of the Moon axis (degrees, [0, 360)). Meeus Ch. 53.';
|
|
||||||
|
|
||||||
CREATE FUNCTION moon_libration(timestamptz,
|
|
||||||
OUT l float8, OUT b float8, OUT p float8) RETURNS record
|
|
||||||
AS 'MODULE_PATHNAME', 'moon_libration'
|
|
||||||
LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
|
||||||
COMMENT ON FUNCTION moon_libration(timestamptz) IS
|
|
||||||
'All three libration values: longitude (l), latitude (b), position angle (p) in degrees.';
|
|
||||||
|
|
||||||
CREATE FUNCTION moon_subsolar_longitude(timestamptz) RETURNS float8
|
|
||||||
AS 'MODULE_PATHNAME', 'moon_subsolar_longitude'
|
|
||||||
LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
|
|
||||||
COMMENT ON FUNCTION moon_subsolar_longitude(timestamptz) IS
|
|
||||||
'Selenographic longitude of the sub-solar point (degrees, [0, 360)). Determines the lunar terminator position.';
|
|
||||||
File diff suppressed because it is too large
Load Diff
@ -1,362 +0,0 @@
|
|||||||
/*
|
|
||||||
* eclipse_funcs.c -- Satellite eclipse prediction
|
|
||||||
*
|
|
||||||
* Determines when a satellite enters/exits Earth's shadow using
|
|
||||||
* a cylindrical shadow model (Vallado, "Fundamentals of
|
|
||||||
* Astrodynamics", Section 5.3).
|
|
||||||
*
|
|
||||||
* Earth casts a cylindrical shadow of radius R_Earth opposite the
|
|
||||||
* Sun direction. A satellite is eclipsed when its perpendicular
|
|
||||||
* distance from the shadow axis is within R_Earth AND it is on the
|
|
||||||
* far side of Earth from the Sun.
|
|
||||||
*
|
|
||||||
* Sun direction computed via VSOP87 (ecliptic J2000 -> equatorial
|
|
||||||
* J2000). TEME differs from J2000 by ~arcsec nutation residual,
|
|
||||||
* negligible at the 6378 km shadow boundary scale.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "postgres.h"
|
|
||||||
#include "fmgr.h"
|
|
||||||
#include "utils/timestamp.h"
|
|
||||||
#include "types.h"
|
|
||||||
#include "astro_math.h"
|
|
||||||
#include "vsop87.h"
|
|
||||||
#include "norad.h"
|
|
||||||
#include <math.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
PG_FUNCTION_INFO_V1(satellite_is_eclipsed);
|
|
||||||
PG_FUNCTION_INFO_V1(satellite_next_eclipse_entry);
|
|
||||||
PG_FUNCTION_INFO_V1(satellite_next_eclipse_exit);
|
|
||||||
PG_FUNCTION_INFO_V1(satellite_eclipse_fraction);
|
|
||||||
|
|
||||||
#define DEG_TO_RAD_EC (M_PI / 180.0)
|
|
||||||
#define RAD_TO_DEG_EC (180.0 / M_PI)
|
|
||||||
|
|
||||||
#define ECLIPSE_SCAN_STEP_JD (30.0 / 86400.0) /* 30 seconds */
|
|
||||||
#define ECLIPSE_BISECT_TOL_JD (0.5 / 86400.0) /* 0.5 second */
|
|
||||||
#define ECLIPSE_SEARCH_DAYS 7.0
|
|
||||||
|
|
||||||
|
|
||||||
/* ----------------------------------------------------------------
|
|
||||||
* Static helpers -- duplicated from pass_funcs.c per project
|
|
||||||
* convention (no cross-TU symbol coupling).
|
|
||||||
* ----------------------------------------------------------------
|
|
||||||
*/
|
|
||||||
|
|
||||||
static void
|
|
||||||
pg_tle_to_sat_code_ec(const pg_tle *src, tle_t *dst)
|
|
||||||
{
|
|
||||||
memset(dst, 0, sizeof(tle_t));
|
|
||||||
dst->epoch = src->epoch;
|
|
||||||
dst->xincl = src->inclination;
|
|
||||||
dst->xnodeo = src->raan;
|
|
||||||
dst->eo = src->eccentricity;
|
|
||||||
dst->omegao = src->arg_perigee;
|
|
||||||
dst->xmo = src->mean_anomaly;
|
|
||||||
dst->xno = src->mean_motion;
|
|
||||||
dst->xndt2o = src->mean_motion_dot;
|
|
||||||
dst->xndd6o = src->mean_motion_ddot;
|
|
||||||
dst->bstar = src->bstar;
|
|
||||||
dst->norad_number = src->norad_id;
|
|
||||||
dst->bulletin_number = src->elset_num;
|
|
||||||
dst->revolution_number = src->rev_num;
|
|
||||||
dst->classification = src->classification;
|
|
||||||
dst->ephemeris_type = src->ephemeris_type;
|
|
||||||
memcpy(dst->intl_desig, src->intl_desig, 9);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
do_propagate_ec(const pg_tle *tle, double jd, double *pos, double *vel)
|
|
||||||
{
|
|
||||||
tle_t sat;
|
|
||||||
double *params;
|
|
||||||
int is_deep;
|
|
||||||
int err;
|
|
||||||
double tsince;
|
|
||||||
|
|
||||||
pg_tle_to_sat_code_ec(tle, &sat);
|
|
||||||
is_deep = select_ephemeris(&sat);
|
|
||||||
if (is_deep < 0)
|
|
||||||
return -99;
|
|
||||||
|
|
||||||
tsince = jd_to_minutes_since_epoch(jd, sat.epoch);
|
|
||||||
params = palloc(sizeof(double) * N_SAT_PARAMS);
|
|
||||||
|
|
||||||
if (is_deep)
|
|
||||||
{
|
|
||||||
SDP4_init(params, &sat);
|
|
||||||
err = SDP4(tsince, &sat, params, pos, vel);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
SGP4_init(params, &sat);
|
|
||||||
err = SGP4(tsince, &sat, params, pos, vel);
|
|
||||||
}
|
|
||||||
|
|
||||||
pfree(params);
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Compute unit vector from Earth to Sun in equatorial J2000.
|
|
||||||
*
|
|
||||||
* Uses VSOP87 Earth position (ecliptic J2000), negates to get
|
|
||||||
* geocentric Sun, rotates to equatorial. Returns unit vector.
|
|
||||||
*/
|
|
||||||
static void
|
|
||||||
sun_direction_equ(double jd, double sun_dir[3])
|
|
||||||
{
|
|
||||||
double earth_xyz[6];
|
|
||||||
double sun_ecl[3], sun_equ[3];
|
|
||||||
double r;
|
|
||||||
|
|
||||||
GetVsop87Coor(jd, 2, earth_xyz); /* VSOP87 body 2 = Earth */
|
|
||||||
|
|
||||||
/* Geocentric Sun = -Earth heliocentric */
|
|
||||||
sun_ecl[0] = -earth_xyz[0];
|
|
||||||
sun_ecl[1] = -earth_xyz[1];
|
|
||||||
sun_ecl[2] = -earth_xyz[2];
|
|
||||||
|
|
||||||
/* Ecliptic J2000 -> equatorial J2000 */
|
|
||||||
ecliptic_to_equatorial(sun_ecl, sun_equ);
|
|
||||||
|
|
||||||
/* Normalize to unit vector */
|
|
||||||
r = sqrt(sun_equ[0] * sun_equ[0] +
|
|
||||||
sun_equ[1] * sun_equ[1] +
|
|
||||||
sun_equ[2] * sun_equ[2]);
|
|
||||||
|
|
||||||
sun_dir[0] = sun_equ[0] / r;
|
|
||||||
sun_dir[1] = sun_equ[1] / r;
|
|
||||||
sun_dir[2] = sun_equ[2] / r;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* is_satellite_eclipsed_pos -- cylindrical shadow test
|
|
||||||
*
|
|
||||||
* sat_pos[3]: satellite position relative to Earth center (km, TEME/J2000)
|
|
||||||
* sun_dir[3]: unit vector from Earth toward Sun (J2000 equatorial)
|
|
||||||
*
|
|
||||||
* Eclipsed when:
|
|
||||||
* 1. sat dot sun_dir < 0 (satellite on shadow side of Earth)
|
|
||||||
* 2. perpendicular distance from shadow axis < R_Earth
|
|
||||||
*/
|
|
||||||
static bool
|
|
||||||
is_satellite_eclipsed_pos(const double sat_pos[3], const double sun_dir[3])
|
|
||||||
{
|
|
||||||
double proj, perp[3], perp_dist;
|
|
||||||
|
|
||||||
/* Project satellite position onto Sun direction */
|
|
||||||
proj = sat_pos[0] * sun_dir[0] +
|
|
||||||
sat_pos[1] * sun_dir[1] +
|
|
||||||
sat_pos[2] * sun_dir[2];
|
|
||||||
|
|
||||||
if (proj > 0.0)
|
|
||||||
return false; /* sunlit side of Earth */
|
|
||||||
|
|
||||||
/* Perpendicular vector from shadow axis */
|
|
||||||
perp[0] = sat_pos[0] - proj * sun_dir[0];
|
|
||||||
perp[1] = sat_pos[1] - proj * sun_dir[1];
|
|
||||||
perp[2] = sat_pos[2] - proj * sun_dir[2];
|
|
||||||
perp_dist = sqrt(perp[0] * perp[0] +
|
|
||||||
perp[1] * perp[1] +
|
|
||||||
perp[2] * perp[2]);
|
|
||||||
|
|
||||||
return (perp_dist < WGS84_A); /* 6378.137 km */
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* eclipse_state_at_jd -- compute eclipse state at a single time
|
|
||||||
*
|
|
||||||
* Returns true if eclipsed, false if sunlit.
|
|
||||||
* Returns false on propagation error (conservative: assume sunlit).
|
|
||||||
*/
|
|
||||||
static bool
|
|
||||||
eclipse_state_at_jd(const pg_tle *tle, double jd)
|
|
||||||
{
|
|
||||||
double pos[3], vel[3];
|
|
||||||
double sun_dir[3];
|
|
||||||
int err;
|
|
||||||
|
|
||||||
err = do_propagate_ec(tle, jd, pos, vel);
|
|
||||||
if (err != 0)
|
|
||||||
return false; /* propagation failed, assume sunlit */
|
|
||||||
|
|
||||||
sun_direction_equ(jd, sun_dir);
|
|
||||||
|
|
||||||
return is_satellite_eclipsed_pos(pos, sun_dir);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* ================================================================
|
|
||||||
* satellite_is_eclipsed(tle, timestamptz) -> bool
|
|
||||||
*
|
|
||||||
* Point-in-time eclipse test. Returns true if the satellite is
|
|
||||||
* in Earth's cylindrical shadow at the given time.
|
|
||||||
* ================================================================
|
|
||||||
*/
|
|
||||||
Datum
|
|
||||||
satellite_is_eclipsed(PG_FUNCTION_ARGS)
|
|
||||||
{
|
|
||||||
pg_tle *tle = (pg_tle *) PG_GETARG_POINTER(0);
|
|
||||||
int64 ts = PG_GETARG_INT64(1);
|
|
||||||
double jd;
|
|
||||||
|
|
||||||
jd = timestamptz_to_jd(ts);
|
|
||||||
|
|
||||||
PG_RETURN_BOOL(eclipse_state_at_jd(tle, jd));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* ================================================================
|
|
||||||
* satellite_next_eclipse_entry(tle, timestamptz) -> timestamptz
|
|
||||||
*
|
|
||||||
* Scans forward from the given time to find when the satellite
|
|
||||||
* next enters Earth's shadow. Searches up to 7 days.
|
|
||||||
* Returns NULL if no eclipse entry is found.
|
|
||||||
* ================================================================
|
|
||||||
*/
|
|
||||||
Datum
|
|
||||||
satellite_next_eclipse_entry(PG_FUNCTION_ARGS)
|
|
||||||
{
|
|
||||||
pg_tle *tle = (pg_tle *) PG_GETARG_POINTER(0);
|
|
||||||
int64 ts = PG_GETARG_INT64(1);
|
|
||||||
double jd, stop_jd;
|
|
||||||
bool prev_eclipsed, curr_eclipsed;
|
|
||||||
double lo, hi, mid;
|
|
||||||
|
|
||||||
jd = timestamptz_to_jd(ts);
|
|
||||||
stop_jd = jd + ECLIPSE_SEARCH_DAYS;
|
|
||||||
|
|
||||||
prev_eclipsed = eclipse_state_at_jd(tle, jd);
|
|
||||||
|
|
||||||
while (jd < stop_jd)
|
|
||||||
{
|
|
||||||
jd += ECLIPSE_SCAN_STEP_JD;
|
|
||||||
if (jd > stop_jd)
|
|
||||||
jd = stop_jd;
|
|
||||||
|
|
||||||
curr_eclipsed = eclipse_state_at_jd(tle, jd);
|
|
||||||
|
|
||||||
/* Transition from sunlit to eclipsed */
|
|
||||||
if (!prev_eclipsed && curr_eclipsed)
|
|
||||||
{
|
|
||||||
/* Bisect to refine entry time */
|
|
||||||
lo = jd - ECLIPSE_SCAN_STEP_JD;
|
|
||||||
hi = jd;
|
|
||||||
while (hi - lo > ECLIPSE_BISECT_TOL_JD)
|
|
||||||
{
|
|
||||||
mid = (lo + hi) / 2.0;
|
|
||||||
if (eclipse_state_at_jd(tle, mid))
|
|
||||||
hi = mid;
|
|
||||||
else
|
|
||||||
lo = mid;
|
|
||||||
}
|
|
||||||
|
|
||||||
PG_RETURN_TIMESTAMPTZ(jd_to_timestamptz((lo + hi) / 2.0));
|
|
||||||
}
|
|
||||||
|
|
||||||
prev_eclipsed = curr_eclipsed;
|
|
||||||
}
|
|
||||||
|
|
||||||
PG_RETURN_NULL();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* ================================================================
|
|
||||||
* satellite_next_eclipse_exit(tle, timestamptz) -> timestamptz
|
|
||||||
*
|
|
||||||
* Scans forward from the given time to find when the satellite
|
|
||||||
* next exits Earth's shadow (returns to sunlight).
|
|
||||||
* Searches up to 7 days. Returns NULL if no exit found.
|
|
||||||
* ================================================================
|
|
||||||
*/
|
|
||||||
Datum
|
|
||||||
satellite_next_eclipse_exit(PG_FUNCTION_ARGS)
|
|
||||||
{
|
|
||||||
pg_tle *tle = (pg_tle *) PG_GETARG_POINTER(0);
|
|
||||||
int64 ts = PG_GETARG_INT64(1);
|
|
||||||
double jd, stop_jd;
|
|
||||||
bool prev_eclipsed, curr_eclipsed;
|
|
||||||
double lo, hi, mid;
|
|
||||||
|
|
||||||
jd = timestamptz_to_jd(ts);
|
|
||||||
stop_jd = jd + ECLIPSE_SEARCH_DAYS;
|
|
||||||
|
|
||||||
prev_eclipsed = eclipse_state_at_jd(tle, jd);
|
|
||||||
|
|
||||||
while (jd < stop_jd)
|
|
||||||
{
|
|
||||||
jd += ECLIPSE_SCAN_STEP_JD;
|
|
||||||
if (jd > stop_jd)
|
|
||||||
jd = stop_jd;
|
|
||||||
|
|
||||||
curr_eclipsed = eclipse_state_at_jd(tle, jd);
|
|
||||||
|
|
||||||
/* Transition from eclipsed to sunlit */
|
|
||||||
if (prev_eclipsed && !curr_eclipsed)
|
|
||||||
{
|
|
||||||
/* Bisect to refine exit time */
|
|
||||||
lo = jd - ECLIPSE_SCAN_STEP_JD;
|
|
||||||
hi = jd;
|
|
||||||
while (hi - lo > ECLIPSE_BISECT_TOL_JD)
|
|
||||||
{
|
|
||||||
mid = (lo + hi) / 2.0;
|
|
||||||
if (eclipse_state_at_jd(tle, mid))
|
|
||||||
lo = mid;
|
|
||||||
else
|
|
||||||
hi = mid;
|
|
||||||
}
|
|
||||||
|
|
||||||
PG_RETURN_TIMESTAMPTZ(jd_to_timestamptz((lo + hi) / 2.0));
|
|
||||||
}
|
|
||||||
|
|
||||||
prev_eclipsed = curr_eclipsed;
|
|
||||||
}
|
|
||||||
|
|
||||||
PG_RETURN_NULL();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* ================================================================
|
|
||||||
* satellite_eclipse_fraction(tle, timestamptz, timestamptz) -> float8
|
|
||||||
*
|
|
||||||
* Fraction of the given time window spent in eclipse [0.0, 1.0].
|
|
||||||
* Scans the window at 30-second intervals and counts eclipsed samples.
|
|
||||||
*
|
|
||||||
* Useful for determining what portion of a pass is in shadow.
|
|
||||||
* ================================================================
|
|
||||||
*/
|
|
||||||
Datum
|
|
||||||
satellite_eclipse_fraction(PG_FUNCTION_ARGS)
|
|
||||||
{
|
|
||||||
pg_tle *tle = (pg_tle *) PG_GETARG_POINTER(0);
|
|
||||||
int64 start_ts = PG_GETARG_INT64(1);
|
|
||||||
int64 stop_ts = PG_GETARG_INT64(2);
|
|
||||||
double start_jd, stop_jd, jd;
|
|
||||||
int total_samples = 0;
|
|
||||||
int eclipsed_samples = 0;
|
|
||||||
|
|
||||||
if (stop_ts <= start_ts)
|
|
||||||
ereport(ERROR,
|
|
||||||
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
|
||||||
errmsg("satellite_eclipse_fraction: stop time must be after start time")));
|
|
||||||
|
|
||||||
start_jd = timestamptz_to_jd(start_ts);
|
|
||||||
stop_jd = timestamptz_to_jd(stop_ts);
|
|
||||||
|
|
||||||
for (jd = start_jd; jd <= stop_jd; jd += ECLIPSE_SCAN_STEP_JD)
|
|
||||||
{
|
|
||||||
if (eclipse_state_at_jd(tle, jd))
|
|
||||||
eclipsed_samples++;
|
|
||||||
total_samples++;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (total_samples == 0)
|
|
||||||
PG_RETURN_FLOAT8(0.0);
|
|
||||||
|
|
||||||
PG_RETURN_FLOAT8((double) eclipsed_samples / (double) total_samples);
|
|
||||||
}
|
|
||||||
@ -1,22 +0,0 @@
|
|||||||
/*
|
|
||||||
* libration.h -- Lunar optical libration (Meeus Ch. 53)
|
|
||||||
*
|
|
||||||
* Three components of the Moon's apparent wobble:
|
|
||||||
* l -- optical libration in longitude (degrees, [-8, +8])
|
|
||||||
* b -- optical libration in latitude (degrees, [-7, +7])
|
|
||||||
* p -- position angle of the Moon's axis (degrees)
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef PG_ORRERY_LIBRATION_H
|
|
||||||
#define PG_ORRERY_LIBRATION_H
|
|
||||||
|
|
||||||
typedef struct
|
|
||||||
{
|
|
||||||
double l; /* libration in longitude, degrees */
|
|
||||||
double b; /* libration in latitude, degrees */
|
|
||||||
double p; /* position angle of axis, degrees */
|
|
||||||
} lunar_libration;
|
|
||||||
|
|
||||||
void compute_lunar_libration(double jd, lunar_libration *lib);
|
|
||||||
|
|
||||||
#endif /* PG_ORRERY_LIBRATION_H */
|
|
||||||
@ -1,368 +0,0 @@
|
|||||||
/*
|
|
||||||
* libration_funcs.c -- Lunar libration and subsolar longitude
|
|
||||||
*
|
|
||||||
* Optical libration of the Moon (apparent wobble) computed from
|
|
||||||
* Meeus (1998) "Astronomical Algorithms", Chapter 53.
|
|
||||||
*
|
|
||||||
* Three components:
|
|
||||||
* l' -- libration in longitude (degrees, typically [-8, +8])
|
|
||||||
* b' -- libration in latitude (degrees, typically [-7, +7])
|
|
||||||
* P -- position angle of the Moon's axis (degrees)
|
|
||||||
*
|
|
||||||
* Also: selenographic subsolar longitude (terminator position).
|
|
||||||
*
|
|
||||||
* References:
|
|
||||||
* Meeus (1998) Chapters 22, 47, 53
|
|
||||||
* Chapront-Touze & Chapront (1988) ELP2000-82B
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "postgres.h"
|
|
||||||
#include "fmgr.h"
|
|
||||||
#include "funcapi.h"
|
|
||||||
#include "utils/timestamp.h"
|
|
||||||
#include "types.h"
|
|
||||||
#include "astro_math.h"
|
|
||||||
#include "elp82b.h"
|
|
||||||
#include "vsop87.h"
|
|
||||||
#include "precession.h"
|
|
||||||
#include "libration.h"
|
|
||||||
#include <math.h>
|
|
||||||
|
|
||||||
PG_FUNCTION_INFO_V1(moon_libration_longitude);
|
|
||||||
PG_FUNCTION_INFO_V1(moon_libration_latitude);
|
|
||||||
PG_FUNCTION_INFO_V1(moon_libration_position_angle);
|
|
||||||
PG_FUNCTION_INFO_V1(moon_libration);
|
|
||||||
PG_FUNCTION_INFO_V1(moon_subsolar_longitude);
|
|
||||||
|
|
||||||
|
|
||||||
/* Mean inclination of the lunar equator to the ecliptic (Meeus Ch. 53) */
|
|
||||||
#define LUNAR_I_RAD (1.54242 * M_PI / 180.0) /* 1.54242 degrees */
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Moon's mean longitude referred to the mean equinox of date (F)
|
|
||||||
* and longitude of the ascending node (Omega).
|
|
||||||
*
|
|
||||||
* Meeus (1998) Table 22.A, using T = Julian centuries from J2000.
|
|
||||||
* F = mean longitude - RAAN (Meeus notation: F is the argument
|
|
||||||
* of latitude = mean anomaly + arg of perigee; but for libration
|
|
||||||
* we need the mean longitude L' = F + Omega).
|
|
||||||
*/
|
|
||||||
static void
|
|
||||||
lunar_fundamental_args(double jd, double *F_out, double *Omega_out,
|
|
||||||
double *Lprime_out)
|
|
||||||
{
|
|
||||||
double T = (jd - J2000_JD) / 36525.0;
|
|
||||||
double T2 = T * T;
|
|
||||||
double T3 = T2 * T;
|
|
||||||
double T4 = T3 * T;
|
|
||||||
double Lprime, F, Omega;
|
|
||||||
|
|
||||||
/* Moon's mean longitude L' (Meeus Eq. 47.1) */
|
|
||||||
Lprime = 218.3164477
|
|
||||||
+ 481267.88123421 * T
|
|
||||||
- 0.0015786 * T2
|
|
||||||
+ T3 / 538841.0
|
|
||||||
- T4 / 65194000.0;
|
|
||||||
|
|
||||||
/* Moon's argument of latitude F (Meeus Eq. 47.5) */
|
|
||||||
F = 93.2720950
|
|
||||||
+ 483202.0175233 * T
|
|
||||||
- 0.0036539 * T2
|
|
||||||
- T3 / 3526000.0
|
|
||||||
+ T4 / 863310000.0;
|
|
||||||
|
|
||||||
/* Longitude of the ascending node Omega (Meeus Eq. 47.7) */
|
|
||||||
Omega = 125.0445479
|
|
||||||
- 1934.1362891 * T
|
|
||||||
+ 0.0020754 * T2
|
|
||||||
+ T3 / 467441.0
|
|
||||||
- T4 / 60616000.0;
|
|
||||||
|
|
||||||
/* Normalize to [0, 360) */
|
|
||||||
Lprime = fmod(Lprime, 360.0);
|
|
||||||
if (Lprime < 0.0) Lprime += 360.0;
|
|
||||||
|
|
||||||
F = fmod(F, 360.0);
|
|
||||||
if (F < 0.0) F += 360.0;
|
|
||||||
|
|
||||||
Omega = fmod(Omega, 360.0);
|
|
||||||
if (Omega < 0.0) Omega += 360.0;
|
|
||||||
|
|
||||||
*F_out = F;
|
|
||||||
*Omega_out = Omega;
|
|
||||||
*Lprime_out = Lprime;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* compute_lunar_libration -- Meeus (1998) Ch. 53
|
|
||||||
*
|
|
||||||
* Computes optical libration from the Moon's ecliptic coordinates,
|
|
||||||
* mean longitude, ascending node, and nutation.
|
|
||||||
*/
|
|
||||||
void
|
|
||||||
compute_lunar_libration(double jd, lunar_libration *lib)
|
|
||||||
{
|
|
||||||
double moon_ecl[3];
|
|
||||||
double lambda, beta; /* geocentric ecliptic long/lat, radians */
|
|
||||||
double F_deg, Omega_deg, Lprime_deg;
|
|
||||||
double F, Omega; /* radians */
|
|
||||||
double dpsi, deps; /* nutation, arcseconds */
|
|
||||||
double eps_A, chi_A, omega_A, psi_A; /* precession, arcseconds */
|
|
||||||
double eps_rad; /* mean obliquity of date, radians */
|
|
||||||
double W; /* intermediate angle */
|
|
||||||
double A, l_prime, b_prime;
|
|
||||||
double sin_W, cos_W;
|
|
||||||
double sin_beta, cos_beta;
|
|
||||||
double sin_I = sin(LUNAR_I_RAD);
|
|
||||||
double cos_I = cos(LUNAR_I_RAD);
|
|
||||||
double V, X, P;
|
|
||||||
double ra_moon;
|
|
||||||
|
|
||||||
/* Moon geocentric ecliptic (ELP2000-82B gives ecliptic J2000 in AU) */
|
|
||||||
GetElp82bCoor(jd, moon_ecl);
|
|
||||||
|
|
||||||
/* Cartesian -> spherical ecliptic */
|
|
||||||
lambda = atan2(moon_ecl[1], moon_ecl[0]);
|
|
||||||
if (lambda < 0.0) lambda += 2.0 * M_PI;
|
|
||||||
beta = asin(moon_ecl[2] / sqrt(moon_ecl[0] * moon_ecl[0] +
|
|
||||||
moon_ecl[1] * moon_ecl[1] +
|
|
||||||
moon_ecl[2] * moon_ecl[2]));
|
|
||||||
|
|
||||||
/* Fundamental arguments */
|
|
||||||
lunar_fundamental_args(jd, &F_deg, &Omega_deg, &Lprime_deg);
|
|
||||||
F = F_deg * DEG_TO_RAD;
|
|
||||||
Omega = Omega_deg * DEG_TO_RAD;
|
|
||||||
|
|
||||||
/* Nutation in longitude */
|
|
||||||
get_nutation_angles_iau2000b(jd, &dpsi, &deps);
|
|
||||||
|
|
||||||
/* Mean obliquity of date */
|
|
||||||
get_precession_angles_vondrak(jd, &eps_A, &chi_A, &omega_A, &psi_A);
|
|
||||||
eps_rad = eps_A * ARCSEC_TO_RAD;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Meeus Ch. 53 formulas.
|
|
||||||
*
|
|
||||||
* W = lambda - dpsi - Omega
|
|
||||||
* where lambda is the Moon's geocentric ecliptic longitude,
|
|
||||||
* dpsi is nutation in longitude, and Omega is the ascending node.
|
|
||||||
*
|
|
||||||
* Note: lambda from ELP2000-82B is in J2000 ecliptic frame.
|
|
||||||
* For the libration formulas we need the apparent longitude,
|
|
||||||
* which requires adding nutation. Since W subtracts dpsi
|
|
||||||
* anyway, the J2000 value works: W = lambda_J2000 - Omega.
|
|
||||||
* The dpsi terms cancel when using the geometric longitude.
|
|
||||||
*/
|
|
||||||
W = lambda - Omega;
|
|
||||||
|
|
||||||
sin_W = sin(W);
|
|
||||||
cos_W = cos(W);
|
|
||||||
sin_beta = sin(beta);
|
|
||||||
cos_beta = cos(beta);
|
|
||||||
|
|
||||||
/* Optical libration in longitude (Meeus Eq. 53.1) */
|
|
||||||
A = atan2(sin_W * cos_beta * cos_I - sin_beta * sin_I,
|
|
||||||
cos_W * cos_beta);
|
|
||||||
l_prime = A - F;
|
|
||||||
|
|
||||||
/* Normalize to [-pi, pi) */
|
|
||||||
l_prime = fmod(l_prime + M_PI, 2.0 * M_PI);
|
|
||||||
if (l_prime < 0.0) l_prime += 2.0 * M_PI;
|
|
||||||
l_prime -= M_PI;
|
|
||||||
|
|
||||||
/* Optical libration in latitude (Meeus Eq. 53.2) */
|
|
||||||
b_prime = asin(-sin_W * cos_beta * sin_I - sin_beta * cos_I);
|
|
||||||
|
|
||||||
/* Position angle of the Moon's axis (Meeus Eq. 53.3) */
|
|
||||||
V = Omega + dpsi * ARCSEC_TO_RAD + (eps_rad + deps * ARCSEC_TO_RAD) * 0.0;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* For the position angle P, we need the Moon's RA and Dec.
|
|
||||||
* Compute from ecliptic coordinates with nutation.
|
|
||||||
*/
|
|
||||||
{
|
|
||||||
double lambda_app, sin_eps, cos_eps;
|
|
||||||
|
|
||||||
lambda_app = lambda + dpsi * ARCSEC_TO_RAD;
|
|
||||||
sin_eps = sin(eps_rad + deps * ARCSEC_TO_RAD);
|
|
||||||
cos_eps = cos(eps_rad + deps * ARCSEC_TO_RAD);
|
|
||||||
|
|
||||||
ra_moon = atan2(sin(lambda_app) * cos_eps - tan(beta) * sin_eps,
|
|
||||||
cos(lambda_app));
|
|
||||||
if (ra_moon < 0.0) ra_moon += 2.0 * M_PI;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Position angle (Meeus Eq. 53.3):
|
|
||||||
* V = Omega + dpsi + eps * 0 (simplified; V uses Omega + dpsi)
|
|
||||||
* X = (Omega + dpsi) * cos(eps+deps) + ... but Meeus gives:
|
|
||||||
*
|
|
||||||
* Simplified: the position angle depends on the node longitude
|
|
||||||
* projected through the equatorial frame.
|
|
||||||
*/
|
|
||||||
V = Omega + dpsi * ARCSEC_TO_RAD;
|
|
||||||
X = sin(V + eps_rad + deps * ARCSEC_TO_RAD);
|
|
||||||
|
|
||||||
P = asin(-X * cos(ra_moon) / cos(b_prime))
|
|
||||||
+ atan2(-sin_I * sin(V - Omega),
|
|
||||||
cos_I * sin_beta - sin_I * cos_beta * cos_W);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Physical libration corrections (Meeus p. 373) are small
|
|
||||||
* (~0.02 deg) and omitted here for the optical model.
|
|
||||||
*/
|
|
||||||
|
|
||||||
lib->l = l_prime * RAD_TO_DEG;
|
|
||||||
lib->b = b_prime * RAD_TO_DEG;
|
|
||||||
lib->p = fmod(P * RAD_TO_DEG, 360.0);
|
|
||||||
if (lib->p < 0.0)
|
|
||||||
lib->p += 360.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* ================================================================
|
|
||||||
* moon_libration_longitude(timestamptz) -> float8
|
|
||||||
* Optical libration in longitude (degrees, typically [-8, +8]).
|
|
||||||
* ================================================================
|
|
||||||
*/
|
|
||||||
Datum
|
|
||||||
moon_libration_longitude(PG_FUNCTION_ARGS)
|
|
||||||
{
|
|
||||||
int64 ts = PG_GETARG_INT64(0);
|
|
||||||
double jd;
|
|
||||||
lunar_libration lib;
|
|
||||||
|
|
||||||
jd = timestamptz_to_jd(ts);
|
|
||||||
compute_lunar_libration(jd, &lib);
|
|
||||||
|
|
||||||
PG_RETURN_FLOAT8(lib.l);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* ================================================================
|
|
||||||
* moon_libration_latitude(timestamptz) -> float8
|
|
||||||
* Optical libration in latitude (degrees, typically [-7, +7]).
|
|
||||||
* ================================================================
|
|
||||||
*/
|
|
||||||
Datum
|
|
||||||
moon_libration_latitude(PG_FUNCTION_ARGS)
|
|
||||||
{
|
|
||||||
int64 ts = PG_GETARG_INT64(0);
|
|
||||||
double jd;
|
|
||||||
lunar_libration lib;
|
|
||||||
|
|
||||||
jd = timestamptz_to_jd(ts);
|
|
||||||
compute_lunar_libration(jd, &lib);
|
|
||||||
|
|
||||||
PG_RETURN_FLOAT8(lib.b);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* ================================================================
|
|
||||||
* moon_libration_position_angle(timestamptz) -> float8
|
|
||||||
* Position angle of the Moon's axis (degrees, [0, 360)).
|
|
||||||
* ================================================================
|
|
||||||
*/
|
|
||||||
Datum
|
|
||||||
moon_libration_position_angle(PG_FUNCTION_ARGS)
|
|
||||||
{
|
|
||||||
int64 ts = PG_GETARG_INT64(0);
|
|
||||||
double jd;
|
|
||||||
lunar_libration lib;
|
|
||||||
|
|
||||||
jd = timestamptz_to_jd(ts);
|
|
||||||
compute_lunar_libration(jd, &lib);
|
|
||||||
|
|
||||||
PG_RETURN_FLOAT8(lib.p);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* ================================================================
|
|
||||||
* moon_libration(timestamptz) -> record (l float8, b float8, p float8)
|
|
||||||
* All three libration values as a composite return.
|
|
||||||
* ================================================================
|
|
||||||
*/
|
|
||||||
Datum
|
|
||||||
moon_libration(PG_FUNCTION_ARGS)
|
|
||||||
{
|
|
||||||
int64 ts = PG_GETARG_INT64(0);
|
|
||||||
double jd;
|
|
||||||
lunar_libration lib;
|
|
||||||
TupleDesc tupdesc;
|
|
||||||
Datum values[3];
|
|
||||||
bool nulls[3] = {false, false, false};
|
|
||||||
|
|
||||||
jd = timestamptz_to_jd(ts);
|
|
||||||
compute_lunar_libration(jd, &lib);
|
|
||||||
|
|
||||||
if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
|
|
||||||
ereport(ERROR,
|
|
||||||
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
||||||
errmsg("function returning record called in context "
|
|
||||||
"that cannot accept type record")));
|
|
||||||
|
|
||||||
tupdesc = BlessTupleDesc(tupdesc);
|
|
||||||
|
|
||||||
values[0] = Float8GetDatum(lib.l);
|
|
||||||
values[1] = Float8GetDatum(lib.b);
|
|
||||||
values[2] = Float8GetDatum(lib.p);
|
|
||||||
|
|
||||||
PG_RETURN_DATUM(HeapTupleGetDatum(
|
|
||||||
heap_form_tuple(tupdesc, values, nulls)));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* ================================================================
|
|
||||||
* moon_subsolar_longitude(timestamptz) -> float8
|
|
||||||
*
|
|
||||||
* Selenographic longitude of the sub-solar point (degrees, [0, 360)).
|
|
||||||
* Determines the terminator position on the Moon.
|
|
||||||
*
|
|
||||||
* This is the libration in longitude plus the selenographic
|
|
||||||
* colongitude of the Sun. The subsolar point's longitude
|
|
||||||
* tracks through 360 deg over a synodic month.
|
|
||||||
*
|
|
||||||
* Simplified computation: the subsolar longitude is approximately
|
|
||||||
* the difference between the Sun's ecliptic longitude and the Moon's
|
|
||||||
* ecliptic longitude, corrected for libration.
|
|
||||||
* ================================================================
|
|
||||||
*/
|
|
||||||
Datum
|
|
||||||
moon_subsolar_longitude(PG_FUNCTION_ARGS)
|
|
||||||
{
|
|
||||||
int64 ts = PG_GETARG_INT64(0);
|
|
||||||
double jd;
|
|
||||||
double earth_xyz[6], moon_ecl[3];
|
|
||||||
double sun_lon, moon_lon;
|
|
||||||
double subsolar;
|
|
||||||
lunar_libration lib;
|
|
||||||
|
|
||||||
jd = timestamptz_to_jd(ts);
|
|
||||||
|
|
||||||
/* Sun's geocentric ecliptic longitude */
|
|
||||||
GetVsop87Coor(jd, 2, earth_xyz);
|
|
||||||
sun_lon = atan2(-earth_xyz[1], -earth_xyz[0]);
|
|
||||||
if (sun_lon < 0.0) sun_lon += 2.0 * M_PI;
|
|
||||||
|
|
||||||
/* Moon's geocentric ecliptic longitude */
|
|
||||||
GetElp82bCoor(jd, moon_ecl);
|
|
||||||
moon_lon = atan2(moon_ecl[1], moon_ecl[0]);
|
|
||||||
if (moon_lon < 0.0) moon_lon += 2.0 * M_PI;
|
|
||||||
|
|
||||||
/* Libration correction */
|
|
||||||
compute_lunar_libration(jd, &lib);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Subsolar longitude on the Moon's surface:
|
|
||||||
* The Sun illuminates from the direction (sun_lon - moon_lon)
|
|
||||||
* as seen from the Moon, corrected for libration.
|
|
||||||
*/
|
|
||||||
subsolar = (sun_lon - moon_lon) * RAD_TO_DEG + lib.l;
|
|
||||||
subsolar = fmod(subsolar, 360.0);
|
|
||||||
if (subsolar < 0.0)
|
|
||||||
subsolar += 360.0;
|
|
||||||
|
|
||||||
PG_RETURN_FLOAT8(subsolar);
|
|
||||||
}
|
|
||||||
@ -1,12 +1,9 @@
|
|||||||
/*
|
/*
|
||||||
* magnitude_funcs.c -- Planet magnitude, solar elongation, phase fraction
|
* magnitude_funcs.c -- Planet apparent visual magnitude
|
||||||
*
|
*
|
||||||
* Uses the Mallama & Hilton (2018) magnitude model with
|
* Uses the Mallama & Hilton (2018) magnitude model with
|
||||||
* VSOP87 positions for distances and phase angles.
|
* VSOP87 positions for distances and phase angles.
|
||||||
*
|
*
|
||||||
* Solar elongation and planet phase reuse the same Sun-Planet-Earth
|
|
||||||
* triangle geometry, factored into compute_planet_geometry().
|
|
||||||
*
|
|
||||||
* Reference: Mallama & Hilton, "Computing Apparent Planetary
|
* Reference: Mallama & Hilton, "Computing Apparent Planetary
|
||||||
* Magnitudes for The Astronomical Almanac", A&C vol. 25, 2018.
|
* Magnitudes for The Astronomical Almanac", A&C vol. 25, 2018.
|
||||||
*/
|
*/
|
||||||
@ -20,8 +17,6 @@
|
|||||||
#include <math.h>
|
#include <math.h>
|
||||||
|
|
||||||
PG_FUNCTION_INFO_V1(planet_magnitude);
|
PG_FUNCTION_INFO_V1(planet_magnitude);
|
||||||
PG_FUNCTION_INFO_V1(solar_elongation);
|
|
||||||
PG_FUNCTION_INFO_V1(planet_phase);
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -122,74 +117,52 @@ static const double planet_v10[] = {
|
|||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Shared Sun-Planet-Earth geometry.
|
* Compute apparent visual magnitude of a planet from Earth.
|
||||||
*
|
*
|
||||||
* Computes the three distances (r, delta, R) and the phase angle
|
* Phase angle is the Sun-Planet-Earth angle, computed via the law
|
||||||
* (Sun-Planet-Earth angle) from VSOP87 positions. Used by
|
* of cosines from three heliocentric/geocentric distances.
|
||||||
* magnitude, elongation, and phase functions.
|
|
||||||
*/
|
*/
|
||||||
typedef struct
|
static double
|
||||||
{
|
compute_planet_magnitude(int body_id, double jd)
|
||||||
double r; /* Sun-Planet distance (AU) */
|
|
||||||
double delta; /* Earth-Planet distance (AU) */
|
|
||||||
double R; /* Sun-Earth distance (AU) */
|
|
||||||
double i_deg; /* Phase angle, degrees (Sun-Planet-Earth vertex) */
|
|
||||||
} planet_geometry;
|
|
||||||
|
|
||||||
static void
|
|
||||||
compute_planet_geometry(int body_id, double jd, planet_geometry *geo)
|
|
||||||
{
|
{
|
||||||
double earth_xyz[6], planet_xyz[6];
|
double earth_xyz[6], planet_xyz[6];
|
||||||
double gv[3];
|
double geo[3];
|
||||||
double cos_i;
|
double r, delta, R;
|
||||||
|
double cos_i, i_deg;
|
||||||
|
double V;
|
||||||
int vsop_body = body_id - 1; /* pg_orrery 1-based -> VSOP87 0-based */
|
int vsop_body = body_id - 1; /* pg_orrery 1-based -> VSOP87 0-based */
|
||||||
|
|
||||||
GetVsop87Coor(jd, 2, earth_xyz); /* Earth (VSOP87 body 2) */
|
GetVsop87Coor(jd, 2, earth_xyz); /* Earth (VSOP87 body 2) */
|
||||||
GetVsop87Coor(jd, vsop_body, planet_xyz); /* target planet */
|
GetVsop87Coor(jd, vsop_body, planet_xyz); /* target planet */
|
||||||
|
|
||||||
/* Heliocentric distance to planet */
|
/* Heliocentric distance to planet */
|
||||||
geo->r = sqrt(planet_xyz[0] * planet_xyz[0] +
|
r = sqrt(planet_xyz[0] * planet_xyz[0] +
|
||||||
planet_xyz[1] * planet_xyz[1] +
|
planet_xyz[1] * planet_xyz[1] +
|
||||||
planet_xyz[2] * planet_xyz[2]);
|
planet_xyz[2] * planet_xyz[2]);
|
||||||
|
|
||||||
/* Geocentric vector and distance */
|
/* Geocentric vector and distance */
|
||||||
gv[0] = planet_xyz[0] - earth_xyz[0];
|
geo[0] = planet_xyz[0] - earth_xyz[0];
|
||||||
gv[1] = planet_xyz[1] - earth_xyz[1];
|
geo[1] = planet_xyz[1] - earth_xyz[1];
|
||||||
gv[2] = planet_xyz[2] - earth_xyz[2];
|
geo[2] = planet_xyz[2] - earth_xyz[2];
|
||||||
geo->delta = sqrt(gv[0] * gv[0] + gv[1] * gv[1] + gv[2] * gv[2]);
|
delta = sqrt(geo[0] * geo[0] + geo[1] * geo[1] + geo[2] * geo[2]);
|
||||||
|
|
||||||
/* Sun-Earth distance */
|
/* Sun-Earth distance */
|
||||||
geo->R = sqrt(earth_xyz[0] * earth_xyz[0] +
|
R = sqrt(earth_xyz[0] * earth_xyz[0] +
|
||||||
earth_xyz[1] * earth_xyz[1] +
|
earth_xyz[1] * earth_xyz[1] +
|
||||||
earth_xyz[2] * earth_xyz[2]);
|
earth_xyz[2] * earth_xyz[2]);
|
||||||
|
|
||||||
/* Phase angle via law of cosines: vertex at planet */
|
/* Phase angle via law of cosines: triangle Sun-Planet-Earth */
|
||||||
cos_i = (geo->r * geo->r + geo->delta * geo->delta - geo->R * geo->R)
|
cos_i = (r * r + delta * delta - R * R) / (2.0 * r * delta);
|
||||||
/ (2.0 * geo->r * geo->delta);
|
|
||||||
if (cos_i > 1.0) cos_i = 1.0;
|
if (cos_i > 1.0) cos_i = 1.0;
|
||||||
if (cos_i < -1.0) cos_i = -1.0;
|
if (cos_i < -1.0) cos_i = -1.0;
|
||||||
geo->i_deg = acos(cos_i) * RAD_TO_DEG;
|
i_deg = acos(cos_i) * RAD_TO_DEG;
|
||||||
}
|
|
||||||
|
|
||||||
|
/* Mallama & Hilton (2018) magnitude with full phase correction */
|
||||||
|
V = planet_v10[body_id]
|
||||||
|
+ 5.0 * log10(r * delta)
|
||||||
|
+ phase_correction(body_id, i_deg);
|
||||||
|
|
||||||
/*
|
return V;
|
||||||
* Validate planet body_id for magnitude/elongation/phase.
|
|
||||||
* Must be 1-8 (Mercury-Neptune), not 3 (Earth).
|
|
||||||
*/
|
|
||||||
static void
|
|
||||||
validate_planet_body_id(int body_id, const char *func_name)
|
|
||||||
{
|
|
||||||
if (body_id < BODY_MERCURY || body_id > BODY_NEPTUNE)
|
|
||||||
ereport(ERROR,
|
|
||||||
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
|
|
||||||
errmsg("%s: body_id %d must be 1-8 (Mercury-Neptune)",
|
|
||||||
func_name, body_id)));
|
|
||||||
|
|
||||||
if (body_id == BODY_EARTH)
|
|
||||||
ereport(ERROR,
|
|
||||||
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
||||||
errmsg("%s: cannot compute for Earth from Earth",
|
|
||||||
func_name)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -209,90 +182,23 @@ validate_planet_body_id(int body_id, const char *func_name)
|
|||||||
Datum
|
Datum
|
||||||
planet_magnitude(PG_FUNCTION_ARGS)
|
planet_magnitude(PG_FUNCTION_ARGS)
|
||||||
{
|
{
|
||||||
int32 body_id = PG_GETARG_INT32(0);
|
int32 body_id = PG_GETARG_INT32(0);
|
||||||
int64 ts = PG_GETARG_INT64(1);
|
int64 ts = PG_GETARG_INT64(1);
|
||||||
double jd;
|
double jd, mag;
|
||||||
planet_geometry geo;
|
|
||||||
double V;
|
|
||||||
|
|
||||||
validate_planet_body_id(body_id, "planet_magnitude");
|
if (body_id < BODY_MERCURY || body_id > BODY_NEPTUNE)
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
|
||||||
|
errmsg("planet_magnitude: 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 compute magnitude for Earth from Earth")));
|
||||||
|
|
||||||
jd = timestamptz_to_jd(ts);
|
jd = timestamptz_to_jd(ts);
|
||||||
compute_planet_geometry(body_id, jd, &geo);
|
mag = compute_planet_magnitude(body_id, jd);
|
||||||
|
|
||||||
V = planet_v10[body_id]
|
PG_RETURN_FLOAT8(mag);
|
||||||
+ 5.0 * log10(geo.r * geo.delta)
|
|
||||||
+ phase_correction(body_id, geo.i_deg);
|
|
||||||
|
|
||||||
PG_RETURN_FLOAT8(V);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* ================================================================
|
|
||||||
* solar_elongation(body_id int4, timestamptz) -> float8
|
|
||||||
*
|
|
||||||
* Sun-Earth-Planet angle in degrees [0, 180].
|
|
||||||
* How far a planet appears from the Sun in the sky.
|
|
||||||
*
|
|
||||||
* Uses law of cosines with vertex at Earth:
|
|
||||||
* cos(elong) = (R^2 + delta^2 - r^2) / (2 * R * delta)
|
|
||||||
*
|
|
||||||
* Mercury max ~28 deg, Venus max ~47 deg.
|
|
||||||
* Superior planets can reach ~180 deg (opposition).
|
|
||||||
* ================================================================
|
|
||||||
*/
|
|
||||||
Datum
|
|
||||||
solar_elongation(PG_FUNCTION_ARGS)
|
|
||||||
{
|
|
||||||
int32 body_id = PG_GETARG_INT32(0);
|
|
||||||
int64 ts = PG_GETARG_INT64(1);
|
|
||||||
double jd;
|
|
||||||
planet_geometry geo;
|
|
||||||
double cos_elong, elong_deg;
|
|
||||||
|
|
||||||
validate_planet_body_id(body_id, "solar_elongation");
|
|
||||||
|
|
||||||
jd = timestamptz_to_jd(ts);
|
|
||||||
compute_planet_geometry(body_id, jd, &geo);
|
|
||||||
|
|
||||||
/* Law of cosines, vertex at Earth */
|
|
||||||
cos_elong = (geo.R * geo.R + geo.delta * geo.delta - geo.r * geo.r)
|
|
||||||
/ (2.0 * geo.R * geo.delta);
|
|
||||||
if (cos_elong > 1.0) cos_elong = 1.0;
|
|
||||||
if (cos_elong < -1.0) cos_elong = -1.0;
|
|
||||||
elong_deg = acos(cos_elong) * RAD_TO_DEG;
|
|
||||||
|
|
||||||
PG_RETURN_FLOAT8(elong_deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* ================================================================
|
|
||||||
* planet_phase(body_id int4, timestamptz) -> float8
|
|
||||||
*
|
|
||||||
* Illuminated fraction of a planet's disk as seen from Earth [0, 1].
|
|
||||||
* k = (1 + cos(i)) / 2
|
|
||||||
* where i is the phase angle (Sun-Planet-Earth).
|
|
||||||
*
|
|
||||||
* Inner planets vary dramatically (Venus crescent).
|
|
||||||
* Outer planets are always near 1.0.
|
|
||||||
* ================================================================
|
|
||||||
*/
|
|
||||||
Datum
|
|
||||||
planet_phase(PG_FUNCTION_ARGS)
|
|
||||||
{
|
|
||||||
int32 body_id = PG_GETARG_INT32(0);
|
|
||||||
int64 ts = PG_GETARG_INT64(1);
|
|
||||||
double jd;
|
|
||||||
planet_geometry geo;
|
|
||||||
double i_rad, k;
|
|
||||||
|
|
||||||
validate_planet_body_id(body_id, "planet_phase");
|
|
||||||
|
|
||||||
jd = timestamptz_to_jd(ts);
|
|
||||||
compute_planet_geometry(body_id, jd, &geo);
|
|
||||||
|
|
||||||
i_rad = geo.i_deg * DEG_TO_RAD;
|
|
||||||
k = (1.0 + cos(i_rad)) / 2.0;
|
|
||||||
|
|
||||||
PG_RETURN_FLOAT8(k);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -238,6 +238,6 @@ FROM (VALUES (1),(2),(4),(5),(6),(7),(8)) AS t(body_id);
|
|||||||
DO $$ BEGIN PERFORM planet_magnitude(0, '2024-01-15 00:00:00+00'::timestamptz); EXCEPTION WHEN OTHERS THEN RAISE NOTICE 'body_id=0(Sun): %', SQLERRM; END $$;
|
DO $$ BEGIN PERFORM planet_magnitude(0, '2024-01-15 00:00:00+00'::timestamptz); EXCEPTION WHEN OTHERS THEN RAISE NOTICE 'body_id=0(Sun): %', SQLERRM; END $$;
|
||||||
NOTICE: body_id=0(Sun): planet_magnitude: body_id 0 must be 1-8 (Mercury-Neptune)
|
NOTICE: body_id=0(Sun): planet_magnitude: body_id 0 must be 1-8 (Mercury-Neptune)
|
||||||
DO $$ BEGIN PERFORM planet_magnitude(3, '2024-01-15 00:00:00+00'::timestamptz); EXCEPTION WHEN OTHERS THEN RAISE NOTICE 'body_id=3(Earth): %', SQLERRM; END $$;
|
DO $$ BEGIN PERFORM planet_magnitude(3, '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): planet_magnitude: cannot compute for Earth from Earth
|
NOTICE: body_id=3(Earth): cannot compute magnitude for Earth from Earth
|
||||||
DO $$ BEGIN PERFORM planet_magnitude(9, '2024-01-15 00:00:00+00'::timestamptz); EXCEPTION WHEN OTHERS THEN RAISE NOTICE 'body_id=9: %', SQLERRM; END $$;
|
DO $$ BEGIN PERFORM planet_magnitude(9, '2024-01-15 00:00:00+00'::timestamptz); EXCEPTION WHEN OTHERS THEN RAISE NOTICE 'body_id=9: %', SQLERRM; END $$;
|
||||||
NOTICE: body_id=9: planet_magnitude: body_id 9 must be 1-8 (Mercury-Neptune)
|
NOTICE: body_id=9: planet_magnitude: body_id 9 must be 1-8 (Mercury-Neptune)
|
||||||
|
|||||||
@ -1,285 +0,0 @@
|
|||||||
-- v017_features.sql -- Tests for v0.17.0: solar elongation, planet phase,
|
|
||||||
-- satellite eclipse, observing night quality, lunar libration
|
|
||||||
--
|
|
||||||
-- Verifies all 12 new functions added in v0.17.0.
|
|
||||||
CREATE EXTENSION IF NOT EXISTS pg_orrery;
|
|
||||||
NOTICE: extension "pg_orrery" already exists, skipping
|
|
||||||
-- ============================================================
|
|
||||||
-- Solar elongation: Mercury always < 28 deg
|
|
||||||
-- ============================================================
|
|
||||||
SELECT solar_elongation(1, '2024-01-15 00:00:00+00'::timestamptz) < 28.0
|
|
||||||
AS mercury_max_elongation;
|
|
||||||
mercury_max_elongation
|
|
||||||
------------------------
|
|
||||||
t
|
|
||||||
(1 row)
|
|
||||||
|
|
||||||
-- ============================================================
|
|
||||||
-- Solar elongation: Venus always < 47 deg
|
|
||||||
-- ============================================================
|
|
||||||
SELECT solar_elongation(2, '2024-01-15 00:00:00+00'::timestamptz) < 47.5
|
|
||||||
AS venus_max_elongation;
|
|
||||||
venus_max_elongation
|
|
||||||
----------------------
|
|
||||||
t
|
|
||||||
(1 row)
|
|
||||||
|
|
||||||
-- ============================================================
|
|
||||||
-- Solar elongation: Mars can exceed 90 deg (superior planet)
|
|
||||||
-- Use a date near opposition (2024-01-12 Mars at elongation ~180)
|
|
||||||
-- At least verify it can be large for outer planets
|
|
||||||
-- ============================================================
|
|
||||||
SELECT solar_elongation(4, '2024-12-08 00:00:00+00'::timestamptz) > 50.0
|
|
||||||
AS mars_large_elongation;
|
|
||||||
mars_large_elongation
|
|
||||||
-----------------------
|
|
||||||
t
|
|
||||||
(1 row)
|
|
||||||
|
|
||||||
-- ============================================================
|
|
||||||
-- Solar elongation: always [0, 180]
|
|
||||||
-- ============================================================
|
|
||||||
SELECT bool_and(
|
|
||||||
solar_elongation(body_id, '2024-06-15 00:00:00+00'::timestamptz) >= 0.0
|
|
||||||
AND solar_elongation(body_id, '2024-06-15 00:00:00+00'::timestamptz) <= 180.0
|
|
||||||
) AS elongation_in_range
|
|
||||||
FROM (VALUES (1),(2),(4),(5),(6),(7),(8)) AS t(body_id);
|
|
||||||
elongation_in_range
|
|
||||||
---------------------
|
|
||||||
t
|
|
||||||
(1 row)
|
|
||||||
|
|
||||||
-- ============================================================
|
|
||||||
-- Solar elongation: error on body_id 0, 3, 9
|
|
||||||
-- ============================================================
|
|
||||||
DO $$ BEGIN PERFORM solar_elongation(0, '2024-01-15 00:00:00+00'::timestamptz); EXCEPTION WHEN OTHERS THEN RAISE NOTICE 'elong body_id=0: %', SQLERRM; END $$;
|
|
||||||
NOTICE: elong body_id=0: solar_elongation: body_id 0 must be 1-8 (Mercury-Neptune)
|
|
||||||
DO $$ BEGIN PERFORM solar_elongation(3, '2024-01-15 00:00:00+00'::timestamptz); EXCEPTION WHEN OTHERS THEN RAISE NOTICE 'elong body_id=3: %', SQLERRM; END $$;
|
|
||||||
NOTICE: elong body_id=3: solar_elongation: cannot compute for Earth from Earth
|
|
||||||
DO $$ BEGIN PERFORM solar_elongation(9, '2024-01-15 00:00:00+00'::timestamptz); EXCEPTION WHEN OTHERS THEN RAISE NOTICE 'elong body_id=9: %', SQLERRM; END $$;
|
|
||||||
NOTICE: elong body_id=9: solar_elongation: body_id 9 must be 1-8 (Mercury-Neptune)
|
|
||||||
-- ============================================================
|
|
||||||
-- Planet phase: Jupiter always near 1.0 (outer planet)
|
|
||||||
-- ============================================================
|
|
||||||
SELECT planet_phase(5, '2024-01-15 00:00:00+00'::timestamptz) > 0.95
|
|
||||||
AS jupiter_nearly_full;
|
|
||||||
jupiter_nearly_full
|
|
||||||
---------------------
|
|
||||||
t
|
|
||||||
(1 row)
|
|
||||||
|
|
||||||
-- ============================================================
|
|
||||||
-- Planet phase: Neptune always near 1.0
|
|
||||||
-- ============================================================
|
|
||||||
SELECT planet_phase(8, '2024-06-15 00:00:00+00'::timestamptz) > 0.99
|
|
||||||
AS neptune_nearly_full;
|
|
||||||
neptune_nearly_full
|
|
||||||
---------------------
|
|
||||||
t
|
|
||||||
(1 row)
|
|
||||||
|
|
||||||
-- ============================================================
|
|
||||||
-- Planet phase: Venus varies significantly (inner planet)
|
|
||||||
-- Check it's in valid range
|
|
||||||
-- ============================================================
|
|
||||||
SELECT planet_phase(2, '2024-06-01 12:00:00+00'::timestamptz) BETWEEN 0.0 AND 1.0
|
|
||||||
AS venus_phase_valid;
|
|
||||||
venus_phase_valid
|
|
||||||
-------------------
|
|
||||||
t
|
|
||||||
(1 row)
|
|
||||||
|
|
||||||
-- ============================================================
|
|
||||||
-- Planet phase: always [0, 1] for all planets
|
|
||||||
-- ============================================================
|
|
||||||
SELECT bool_and(
|
|
||||||
planet_phase(body_id, '2024-01-15 00:00:00+00'::timestamptz) >= 0.0
|
|
||||||
AND planet_phase(body_id, '2024-01-15 00:00:00+00'::timestamptz) <= 1.0
|
|
||||||
) AS phase_in_range
|
|
||||||
FROM (VALUES (1),(2),(4),(5),(6),(7),(8)) AS t(body_id);
|
|
||||||
phase_in_range
|
|
||||||
----------------
|
|
||||||
t
|
|
||||||
(1 row)
|
|
||||||
|
|
||||||
-- ============================================================
|
|
||||||
-- Planet phase: error cases match elongation
|
|
||||||
-- ============================================================
|
|
||||||
DO $$ BEGIN PERFORM planet_phase(3, '2024-01-15 00:00:00+00'::timestamptz); EXCEPTION WHEN OTHERS THEN RAISE NOTICE 'phase body_id=3: %', SQLERRM; END $$;
|
|
||||||
NOTICE: phase body_id=3: planet_phase: cannot compute for Earth from Earth
|
|
||||||
-- ============================================================
|
|
||||||
-- Satellite eclipse: ISS point-in-time test
|
|
||||||
-- (At night the ISS can be eclipsed; just verify function returns bool)
|
|
||||||
-- ============================================================
|
|
||||||
SELECT satellite_is_eclipsed(
|
|
||||||
E'1 25544U 98067A 24001.50000000 .00016717 00000-0 10270-3 0 9025\n2 25544 51.6400 208.9163 0006703 30.1694 61.7520 15.50100486 00001'::tle,
|
|
||||||
'2024-01-01 12:00:00+00'::timestamptz
|
|
||||||
) IS NOT NULL
|
|
||||||
AS eclipse_returns_bool;
|
|
||||||
eclipse_returns_bool
|
|
||||||
----------------------
|
|
||||||
t
|
|
||||||
(1 row)
|
|
||||||
|
|
||||||
-- ============================================================
|
|
||||||
-- Satellite eclipse: next entry/exit return timestamps or NULL
|
|
||||||
-- ============================================================
|
|
||||||
SELECT satellite_next_eclipse_entry(
|
|
||||||
E'1 25544U 98067A 24001.50000000 .00016717 00000-0 10270-3 0 9025\n2 25544 51.6400 208.9163 0006703 30.1694 61.7520 15.50100486 00001'::tle,
|
|
||||||
'2024-01-01 12:00:00+00'::timestamptz
|
|
||||||
) > '2024-01-01 12:00:00+00'::timestamptz
|
|
||||||
AS entry_in_future;
|
|
||||||
entry_in_future
|
|
||||||
-----------------
|
|
||||||
t
|
|
||||||
(1 row)
|
|
||||||
|
|
||||||
SELECT satellite_next_eclipse_exit(
|
|
||||||
E'1 25544U 98067A 24001.50000000 .00016717 00000-0 10270-3 0 9025\n2 25544 51.6400 208.9163 0006703 30.1694 61.7520 15.50100486 00001'::tle,
|
|
||||||
'2024-01-01 12:00:00+00'::timestamptz
|
|
||||||
) > '2024-01-01 12:00:00+00'::timestamptz
|
|
||||||
AS exit_in_future;
|
|
||||||
exit_in_future
|
|
||||||
----------------
|
|
||||||
t
|
|
||||||
(1 row)
|
|
||||||
|
|
||||||
-- ============================================================
|
|
||||||
-- Satellite eclipse: fraction in [0, 1] for a 2-hour window
|
|
||||||
-- ============================================================
|
|
||||||
SELECT satellite_eclipse_fraction(
|
|
||||||
E'1 25544U 98067A 24001.50000000 .00016717 00000-0 10270-3 0 9025\n2 25544 51.6400 208.9163 0006703 30.1694 61.7520 15.50100486 00001'::tle,
|
|
||||||
'2024-01-01 12:00:00+00'::timestamptz,
|
|
||||||
'2024-01-01 14:00:00+00'::timestamptz
|
|
||||||
) BETWEEN 0.0 AND 1.0
|
|
||||||
AS eclipse_fraction_valid;
|
|
||||||
eclipse_fraction_valid
|
|
||||||
------------------------
|
|
||||||
t
|
|
||||||
(1 row)
|
|
||||||
|
|
||||||
-- ============================================================
|
|
||||||
-- Observing night quality: polar summer at 65N = 'poor'
|
|
||||||
-- (no astronomical darkness in June)
|
|
||||||
-- ============================================================
|
|
||||||
SELECT observing_night_quality('(65.0,25.0,0)'::observer, '2024-06-21 12:00:00+00'::timestamptz) = 'poor'
|
|
||||||
AS polar_summer_poor;
|
|
||||||
polar_summer_poor
|
|
||||||
-------------------
|
|
||||||
t
|
|
||||||
(1 row)
|
|
||||||
|
|
||||||
-- ============================================================
|
|
||||||
-- Observing night quality: winter mid-latitude returns valid rating
|
|
||||||
-- ============================================================
|
|
||||||
SELECT observing_night_quality('(43.7,-116.4,800)'::observer, '2024-12-21 12:00:00+00'::timestamptz)
|
|
||||||
IN ('excellent', 'good', 'fair', 'poor')
|
|
||||||
AS winter_valid_rating;
|
|
||||||
winter_valid_rating
|
|
||||||
---------------------
|
|
||||||
t
|
|
||||||
(1 row)
|
|
||||||
|
|
||||||
-- ============================================================
|
|
||||||
-- Lunar libration: longitude in [-8, 8] range
|
|
||||||
-- ============================================================
|
|
||||||
SELECT moon_libration_longitude('2024-01-15 00:00:00+00'::timestamptz) BETWEEN -8.5 AND 8.5
|
|
||||||
AS libration_lon_in_range;
|
|
||||||
libration_lon_in_range
|
|
||||||
------------------------
|
|
||||||
t
|
|
||||||
(1 row)
|
|
||||||
|
|
||||||
SELECT moon_libration_longitude('2024-06-15 00:00:00+00'::timestamptz) BETWEEN -8.5 AND 8.5
|
|
||||||
AS libration_lon_in_range_2;
|
|
||||||
libration_lon_in_range_2
|
|
||||||
--------------------------
|
|
||||||
t
|
|
||||||
(1 row)
|
|
||||||
|
|
||||||
-- ============================================================
|
|
||||||
-- Lunar libration: latitude in [-7, 7] range
|
|
||||||
-- ============================================================
|
|
||||||
SELECT moon_libration_latitude('2024-01-15 00:00:00+00'::timestamptz) BETWEEN -7.5 AND 7.5
|
|
||||||
AS libration_lat_in_range;
|
|
||||||
libration_lat_in_range
|
|
||||||
------------------------
|
|
||||||
t
|
|
||||||
(1 row)
|
|
||||||
|
|
||||||
SELECT moon_libration_latitude('2024-06-15 00:00:00+00'::timestamptz) BETWEEN -7.5 AND 7.5
|
|
||||||
AS libration_lat_in_range_2;
|
|
||||||
libration_lat_in_range_2
|
|
||||||
--------------------------
|
|
||||||
t
|
|
||||||
(1 row)
|
|
||||||
|
|
||||||
-- ============================================================
|
|
||||||
-- Lunar libration: position angle in [0, 360)
|
|
||||||
-- ============================================================
|
|
||||||
SELECT moon_libration_position_angle('2024-01-15 00:00:00+00'::timestamptz) BETWEEN -1.0 AND 361.0
|
|
||||||
AS libration_pa_in_range;
|
|
||||||
libration_pa_in_range
|
|
||||||
-----------------------
|
|
||||||
t
|
|
||||||
(1 row)
|
|
||||||
|
|
||||||
-- ============================================================
|
|
||||||
-- Lunar libration: composite returns same as individual functions
|
|
||||||
-- ============================================================
|
|
||||||
SELECT abs((moon_libration('2024-01-15 00:00:00+00'::timestamptz)).l
|
|
||||||
- moon_libration_longitude('2024-01-15 00:00:00+00'::timestamptz)) < 0.001
|
|
||||||
AS composite_matches_lon;
|
|
||||||
composite_matches_lon
|
|
||||||
-----------------------
|
|
||||||
t
|
|
||||||
(1 row)
|
|
||||||
|
|
||||||
SELECT abs((moon_libration('2024-01-15 00:00:00+00'::timestamptz)).b
|
|
||||||
- moon_libration_latitude('2024-01-15 00:00:00+00'::timestamptz)) < 0.001
|
|
||||||
AS composite_matches_lat;
|
|
||||||
composite_matches_lat
|
|
||||||
-----------------------
|
|
||||||
t
|
|
||||||
(1 row)
|
|
||||||
|
|
||||||
-- ============================================================
|
|
||||||
-- Lunar libration: changes over time (not constant)
|
|
||||||
-- ============================================================
|
|
||||||
SELECT moon_libration_longitude('2024-01-01 00:00:00+00'::timestamptz)
|
|
||||||
!= moon_libration_longitude('2024-01-15 00:00:00+00'::timestamptz)
|
|
||||||
AS libration_varies;
|
|
||||||
libration_varies
|
|
||||||
------------------
|
|
||||||
t
|
|
||||||
(1 row)
|
|
||||||
|
|
||||||
-- ============================================================
|
|
||||||
-- Subsolar longitude: in [0, 360) range
|
|
||||||
-- ============================================================
|
|
||||||
SELECT moon_subsolar_longitude('2024-01-15 00:00:00+00'::timestamptz) BETWEEN 0.0 AND 360.0
|
|
||||||
AS subsolar_in_range;
|
|
||||||
subsolar_in_range
|
|
||||||
-------------------
|
|
||||||
t
|
|
||||||
(1 row)
|
|
||||||
|
|
||||||
SELECT moon_subsolar_longitude('2024-06-15 00:00:00+00'::timestamptz) BETWEEN 0.0 AND 360.0
|
|
||||||
AS subsolar_in_range_2;
|
|
||||||
subsolar_in_range_2
|
|
||||||
---------------------
|
|
||||||
t
|
|
||||||
(1 row)
|
|
||||||
|
|
||||||
-- ============================================================
|
|
||||||
-- Subsolar longitude: changes significantly over synodic month
|
|
||||||
-- (full 360 degrees over ~29.5 days)
|
|
||||||
-- ============================================================
|
|
||||||
SELECT abs(moon_subsolar_longitude('2024-01-01 00:00:00+00'::timestamptz)
|
|
||||||
- moon_subsolar_longitude('2024-01-15 00:00:00+00'::timestamptz)) > 10.0
|
|
||||||
AS subsolar_moves;
|
|
||||||
subsolar_moves
|
|
||||||
----------------
|
|
||||||
t
|
|
||||||
(1 row)
|
|
||||||
|
|
||||||
@ -1,204 +0,0 @@
|
|||||||
-- v017_features.sql -- Tests for v0.17.0: solar elongation, planet phase,
|
|
||||||
-- satellite eclipse, observing night quality, lunar libration
|
|
||||||
--
|
|
||||||
-- Verifies all 12 new functions added in v0.17.0.
|
|
||||||
CREATE EXTENSION IF NOT EXISTS pg_orrery;
|
|
||||||
|
|
||||||
-- ============================================================
|
|
||||||
-- Solar elongation: Mercury always < 28 deg
|
|
||||||
-- ============================================================
|
|
||||||
|
|
||||||
SELECT solar_elongation(1, '2024-01-15 00:00:00+00'::timestamptz) < 28.0
|
|
||||||
AS mercury_max_elongation;
|
|
||||||
|
|
||||||
-- ============================================================
|
|
||||||
-- Solar elongation: Venus always < 47 deg
|
|
||||||
-- ============================================================
|
|
||||||
|
|
||||||
SELECT solar_elongation(2, '2024-01-15 00:00:00+00'::timestamptz) < 47.5
|
|
||||||
AS venus_max_elongation;
|
|
||||||
|
|
||||||
-- ============================================================
|
|
||||||
-- Solar elongation: Mars can exceed 90 deg (superior planet)
|
|
||||||
-- Use a date near opposition (2024-01-12 Mars at elongation ~180)
|
|
||||||
-- At least verify it can be large for outer planets
|
|
||||||
-- ============================================================
|
|
||||||
|
|
||||||
SELECT solar_elongation(4, '2024-12-08 00:00:00+00'::timestamptz) > 50.0
|
|
||||||
AS mars_large_elongation;
|
|
||||||
|
|
||||||
-- ============================================================
|
|
||||||
-- Solar elongation: always [0, 180]
|
|
||||||
-- ============================================================
|
|
||||||
|
|
||||||
SELECT bool_and(
|
|
||||||
solar_elongation(body_id, '2024-06-15 00:00:00+00'::timestamptz) >= 0.0
|
|
||||||
AND solar_elongation(body_id, '2024-06-15 00:00:00+00'::timestamptz) <= 180.0
|
|
||||||
) AS elongation_in_range
|
|
||||||
FROM (VALUES (1),(2),(4),(5),(6),(7),(8)) AS t(body_id);
|
|
||||||
|
|
||||||
-- ============================================================
|
|
||||||
-- Solar elongation: error on body_id 0, 3, 9
|
|
||||||
-- ============================================================
|
|
||||||
|
|
||||||
DO $$ BEGIN PERFORM solar_elongation(0, '2024-01-15 00:00:00+00'::timestamptz); EXCEPTION WHEN OTHERS THEN RAISE NOTICE 'elong body_id=0: %', SQLERRM; END $$;
|
|
||||||
DO $$ BEGIN PERFORM solar_elongation(3, '2024-01-15 00:00:00+00'::timestamptz); EXCEPTION WHEN OTHERS THEN RAISE NOTICE 'elong body_id=3: %', SQLERRM; END $$;
|
|
||||||
DO $$ BEGIN PERFORM solar_elongation(9, '2024-01-15 00:00:00+00'::timestamptz); EXCEPTION WHEN OTHERS THEN RAISE NOTICE 'elong body_id=9: %', SQLERRM; END $$;
|
|
||||||
|
|
||||||
-- ============================================================
|
|
||||||
-- Planet phase: Jupiter always near 1.0 (outer planet)
|
|
||||||
-- ============================================================
|
|
||||||
|
|
||||||
SELECT planet_phase(5, '2024-01-15 00:00:00+00'::timestamptz) > 0.95
|
|
||||||
AS jupiter_nearly_full;
|
|
||||||
|
|
||||||
-- ============================================================
|
|
||||||
-- Planet phase: Neptune always near 1.0
|
|
||||||
-- ============================================================
|
|
||||||
|
|
||||||
SELECT planet_phase(8, '2024-06-15 00:00:00+00'::timestamptz) > 0.99
|
|
||||||
AS neptune_nearly_full;
|
|
||||||
|
|
||||||
-- ============================================================
|
|
||||||
-- Planet phase: Venus varies significantly (inner planet)
|
|
||||||
-- Check it's in valid range
|
|
||||||
-- ============================================================
|
|
||||||
|
|
||||||
SELECT planet_phase(2, '2024-06-01 12:00:00+00'::timestamptz) BETWEEN 0.0 AND 1.0
|
|
||||||
AS venus_phase_valid;
|
|
||||||
|
|
||||||
-- ============================================================
|
|
||||||
-- Planet phase: always [0, 1] for all planets
|
|
||||||
-- ============================================================
|
|
||||||
|
|
||||||
SELECT bool_and(
|
|
||||||
planet_phase(body_id, '2024-01-15 00:00:00+00'::timestamptz) >= 0.0
|
|
||||||
AND planet_phase(body_id, '2024-01-15 00:00:00+00'::timestamptz) <= 1.0
|
|
||||||
) AS phase_in_range
|
|
||||||
FROM (VALUES (1),(2),(4),(5),(6),(7),(8)) AS t(body_id);
|
|
||||||
|
|
||||||
-- ============================================================
|
|
||||||
-- Planet phase: error cases match elongation
|
|
||||||
-- ============================================================
|
|
||||||
|
|
||||||
DO $$ BEGIN PERFORM planet_phase(3, '2024-01-15 00:00:00+00'::timestamptz); EXCEPTION WHEN OTHERS THEN RAISE NOTICE 'phase body_id=3: %', SQLERRM; END $$;
|
|
||||||
|
|
||||||
-- ============================================================
|
|
||||||
-- Satellite eclipse: ISS point-in-time test
|
|
||||||
-- (At night the ISS can be eclipsed; just verify function returns bool)
|
|
||||||
-- ============================================================
|
|
||||||
|
|
||||||
SELECT satellite_is_eclipsed(
|
|
||||||
E'1 25544U 98067A 24001.50000000 .00016717 00000-0 10270-3 0 9025\n2 25544 51.6400 208.9163 0006703 30.1694 61.7520 15.50100486 00001'::tle,
|
|
||||||
'2024-01-01 12:00:00+00'::timestamptz
|
|
||||||
) IS NOT NULL
|
|
||||||
AS eclipse_returns_bool;
|
|
||||||
|
|
||||||
-- ============================================================
|
|
||||||
-- Satellite eclipse: next entry/exit return timestamps or NULL
|
|
||||||
-- ============================================================
|
|
||||||
|
|
||||||
SELECT satellite_next_eclipse_entry(
|
|
||||||
E'1 25544U 98067A 24001.50000000 .00016717 00000-0 10270-3 0 9025\n2 25544 51.6400 208.9163 0006703 30.1694 61.7520 15.50100486 00001'::tle,
|
|
||||||
'2024-01-01 12:00:00+00'::timestamptz
|
|
||||||
) > '2024-01-01 12:00:00+00'::timestamptz
|
|
||||||
AS entry_in_future;
|
|
||||||
|
|
||||||
SELECT satellite_next_eclipse_exit(
|
|
||||||
E'1 25544U 98067A 24001.50000000 .00016717 00000-0 10270-3 0 9025\n2 25544 51.6400 208.9163 0006703 30.1694 61.7520 15.50100486 00001'::tle,
|
|
||||||
'2024-01-01 12:00:00+00'::timestamptz
|
|
||||||
) > '2024-01-01 12:00:00+00'::timestamptz
|
|
||||||
AS exit_in_future;
|
|
||||||
|
|
||||||
-- ============================================================
|
|
||||||
-- Satellite eclipse: fraction in [0, 1] for a 2-hour window
|
|
||||||
-- ============================================================
|
|
||||||
|
|
||||||
SELECT satellite_eclipse_fraction(
|
|
||||||
E'1 25544U 98067A 24001.50000000 .00016717 00000-0 10270-3 0 9025\n2 25544 51.6400 208.9163 0006703 30.1694 61.7520 15.50100486 00001'::tle,
|
|
||||||
'2024-01-01 12:00:00+00'::timestamptz,
|
|
||||||
'2024-01-01 14:00:00+00'::timestamptz
|
|
||||||
) BETWEEN 0.0 AND 1.0
|
|
||||||
AS eclipse_fraction_valid;
|
|
||||||
|
|
||||||
-- ============================================================
|
|
||||||
-- Observing night quality: polar summer at 65N = 'poor'
|
|
||||||
-- (no astronomical darkness in June)
|
|
||||||
-- ============================================================
|
|
||||||
|
|
||||||
SELECT observing_night_quality('(65.0,25.0,0)'::observer, '2024-06-21 12:00:00+00'::timestamptz) = 'poor'
|
|
||||||
AS polar_summer_poor;
|
|
||||||
|
|
||||||
-- ============================================================
|
|
||||||
-- Observing night quality: winter mid-latitude returns valid rating
|
|
||||||
-- ============================================================
|
|
||||||
|
|
||||||
SELECT observing_night_quality('(43.7,-116.4,800)'::observer, '2024-12-21 12:00:00+00'::timestamptz)
|
|
||||||
IN ('excellent', 'good', 'fair', 'poor')
|
|
||||||
AS winter_valid_rating;
|
|
||||||
|
|
||||||
-- ============================================================
|
|
||||||
-- Lunar libration: longitude in [-8, 8] range
|
|
||||||
-- ============================================================
|
|
||||||
|
|
||||||
SELECT moon_libration_longitude('2024-01-15 00:00:00+00'::timestamptz) BETWEEN -8.5 AND 8.5
|
|
||||||
AS libration_lon_in_range;
|
|
||||||
|
|
||||||
SELECT moon_libration_longitude('2024-06-15 00:00:00+00'::timestamptz) BETWEEN -8.5 AND 8.5
|
|
||||||
AS libration_lon_in_range_2;
|
|
||||||
|
|
||||||
-- ============================================================
|
|
||||||
-- Lunar libration: latitude in [-7, 7] range
|
|
||||||
-- ============================================================
|
|
||||||
|
|
||||||
SELECT moon_libration_latitude('2024-01-15 00:00:00+00'::timestamptz) BETWEEN -7.5 AND 7.5
|
|
||||||
AS libration_lat_in_range;
|
|
||||||
|
|
||||||
SELECT moon_libration_latitude('2024-06-15 00:00:00+00'::timestamptz) BETWEEN -7.5 AND 7.5
|
|
||||||
AS libration_lat_in_range_2;
|
|
||||||
|
|
||||||
-- ============================================================
|
|
||||||
-- Lunar libration: position angle in [0, 360)
|
|
||||||
-- ============================================================
|
|
||||||
|
|
||||||
SELECT moon_libration_position_angle('2024-01-15 00:00:00+00'::timestamptz) BETWEEN -1.0 AND 361.0
|
|
||||||
AS libration_pa_in_range;
|
|
||||||
|
|
||||||
-- ============================================================
|
|
||||||
-- Lunar libration: composite returns same as individual functions
|
|
||||||
-- ============================================================
|
|
||||||
|
|
||||||
SELECT abs((moon_libration('2024-01-15 00:00:00+00'::timestamptz)).l
|
|
||||||
- moon_libration_longitude('2024-01-15 00:00:00+00'::timestamptz)) < 0.001
|
|
||||||
AS composite_matches_lon;
|
|
||||||
|
|
||||||
SELECT abs((moon_libration('2024-01-15 00:00:00+00'::timestamptz)).b
|
|
||||||
- moon_libration_latitude('2024-01-15 00:00:00+00'::timestamptz)) < 0.001
|
|
||||||
AS composite_matches_lat;
|
|
||||||
|
|
||||||
-- ============================================================
|
|
||||||
-- Lunar libration: changes over time (not constant)
|
|
||||||
-- ============================================================
|
|
||||||
|
|
||||||
SELECT moon_libration_longitude('2024-01-01 00:00:00+00'::timestamptz)
|
|
||||||
!= moon_libration_longitude('2024-01-15 00:00:00+00'::timestamptz)
|
|
||||||
AS libration_varies;
|
|
||||||
|
|
||||||
-- ============================================================
|
|
||||||
-- Subsolar longitude: in [0, 360) range
|
|
||||||
-- ============================================================
|
|
||||||
|
|
||||||
SELECT moon_subsolar_longitude('2024-01-15 00:00:00+00'::timestamptz) BETWEEN 0.0 AND 360.0
|
|
||||||
AS subsolar_in_range;
|
|
||||||
|
|
||||||
SELECT moon_subsolar_longitude('2024-06-15 00:00:00+00'::timestamptz) BETWEEN 0.0 AND 360.0
|
|
||||||
AS subsolar_in_range_2;
|
|
||||||
|
|
||||||
-- ============================================================
|
|
||||||
-- Subsolar longitude: changes significantly over synodic month
|
|
||||||
-- (full 360 degrees over ~29.5 days)
|
|
||||||
-- ============================================================
|
|
||||||
|
|
||||||
SELECT abs(moon_subsolar_longitude('2024-01-01 00:00:00+00'::timestamptz)
|
|
||||||
- moon_subsolar_longitude('2024-01-15 00:00:00+00'::timestamptz)) > 10.0
|
|
||||||
AS subsolar_moves;
|
|
||||||
Loading…
x
Reference in New Issue
Block a user