pg_orrery/test/sql/v016_features.sql
Ryan Malloy 46c8a30575 Add v0.16.0: twilight dawn/dusk, lunar phase, planet apparent magnitude
Twilight: 6 functions (civil/nautical/astronomical × dawn/dusk) reusing
the existing find_next_crossing() bisection search with Sun depression
angle thresholds (-6°, -12°, -18°). Returns NULL for polar regions
where the threshold is never reached.

Lunar phase: 4 functions computing Sun-Earth-Moon geometry from VSOP87
+ ELP2000-82B. Phase angle [0,360) via elongation + cross product
z-component for waxing/waning discrimination. 8 named phases in 45°
bins. Moon age approximated from phase angle and mean synodic month.

Planet magnitude: Mallama & Hilton (2018) polynomial model with VSOP87
heliocentric distances and phase angle via law of cosines. All 7
planets (Mercury-Neptune, excluding Earth). Saturn ring tilt not
modeled.

151 → 162 SQL objects. 26 → 27 test suites, all passing.
2026-02-26 12:42:01 -07:00

162 lines
7.3 KiB
SQL

-- v016_features.sql -- Tests for v0.16.0: twilight, lunar phase, planet magnitude
--
-- Verifies twilight dawn/dusk, lunar phase calculations,
-- and planet apparent magnitude.
CREATE EXTENSION IF NOT EXISTS pg_orrery;
-- ============================================================
-- Twilight: ordering (astronomical < nautical < civil < sunrise)
-- Eagle, Idaho on the 2024 summer solstice
-- ============================================================
-- Dawn ordering: astronomical dawn < nautical dawn < civil dawn < sunrise
-- Use midnight MDT (07:00 UTC) so all "next" events land on the same morning
SELECT sun_astronomical_dawn('(43.7,-116.4,800)'::observer, '2024-06-21 07:00:00+00'::timestamptz)
< sun_nautical_dawn('(43.7,-116.4,800)'::observer, '2024-06-21 07:00:00+00'::timestamptz)
AS astro_before_nautical;
SELECT sun_nautical_dawn('(43.7,-116.4,800)'::observer, '2024-06-21 07:00:00+00'::timestamptz)
< sun_civil_dawn('(43.7,-116.4,800)'::observer, '2024-06-21 07:00:00+00'::timestamptz)
AS nautical_before_civil;
SELECT sun_civil_dawn('(43.7,-116.4,800)'::observer, '2024-06-21 07:00:00+00'::timestamptz)
< sun_next_rise('(43.7,-116.4,800)'::observer, '2024-06-21 07:00:00+00'::timestamptz)
AS civil_before_sunrise;
-- Dusk ordering: sunset < civil dusk < nautical dusk < astronomical dusk
-- Noon MDT (18:00 UTC) ensures all dusk events are still ahead
SELECT sun_next_set('(43.7,-116.4,800)'::observer, '2024-06-21 18:00:00+00'::timestamptz)
< sun_civil_dusk('(43.7,-116.4,800)'::observer, '2024-06-21 18:00:00+00'::timestamptz)
AS sunset_before_civil;
SELECT sun_civil_dusk('(43.7,-116.4,800)'::observer, '2024-06-21 18:00:00+00'::timestamptz)
< sun_nautical_dusk('(43.7,-116.4,800)'::observer, '2024-06-21 18:00:00+00'::timestamptz)
AS civil_before_nautical;
SELECT sun_nautical_dusk('(43.7,-116.4,800)'::observer, '2024-06-21 18:00:00+00'::timestamptz)
< sun_astronomical_dusk('(43.7,-116.4,800)'::observer, '2024-06-21 18:00:00+00'::timestamptz)
AS nautical_before_astro;
-- ============================================================
-- Twilight: civil dawn ~30 min before sunrise at mid-latitude
-- ============================================================
SELECT extract(epoch FROM
sun_next_rise('(43.7,-116.4,800)'::observer, '2024-06-21 07:00:00+00'::timestamptz)
- sun_civil_dawn('(43.7,-116.4,800)'::observer, '2024-06-21 07:00:00+00'::timestamptz)
) BETWEEN 1200 AND 3600
AS civil_dawn_reasonable_offset;
-- ============================================================
-- Twilight: high latitude summer -- no astronomical darkness
-- At 60N in June, astronomical dusk should be NULL (never gets dark enough)
-- ============================================================
SELECT sun_astronomical_dusk('(60.0,25.0,0)'::observer, '2024-06-21 00:00:00+00'::timestamptz) IS NULL
AS no_astro_dark_60n_summer;
-- ============================================================
-- Lunar phase: known full moon (2024-01-25 ~17:54 UTC)
-- Phase angle should be near 180 deg, illumination near 1.0
-- ============================================================
SELECT round(moon_phase_angle('2024-01-25 18:00:00+00'::timestamptz)::numeric, 0)
BETWEEN 170 AND 190
AS full_moon_angle_near_180;
SELECT round(moon_illumination('2024-01-25 18:00:00+00'::timestamptz)::numeric, 2)
>= 0.95
AS full_moon_high_illumination;
SELECT moon_phase_name('2024-01-25 18:00:00+00'::timestamptz) = 'full_moon'
AS full_moon_named;
-- ============================================================
-- Lunar phase: known new moon (2024-01-11 ~11:57 UTC)
-- Phase angle should be near 0 or 360, illumination near 0
-- ============================================================
SELECT moon_illumination('2024-01-11 12:00:00+00'::timestamptz)
< 0.05
AS new_moon_low_illumination;
SELECT moon_phase_name('2024-01-11 12:00:00+00'::timestamptz) = 'new_moon'
AS new_moon_named;
-- ============================================================
-- Lunar phase: first quarter (2024-01-18 ~03:53 UTC)
-- Phase angle near 90, illumination near 0.5
-- ============================================================
SELECT round(moon_phase_angle('2024-01-18 04:00:00+00'::timestamptz)::numeric, 0)
BETWEEN 80 AND 100
AS first_quarter_angle_near_90;
SELECT moon_illumination('2024-01-18 04:00:00+00'::timestamptz)
BETWEEN 0.4 AND 0.6
AS first_quarter_half_illuminated;
SELECT moon_phase_name('2024-01-18 04:00:00+00'::timestamptz) = 'first_quarter'
AS first_quarter_named;
-- ============================================================
-- Moon age: new moon has age near 0, full moon near 14.7
-- ============================================================
SELECT moon_age('2024-01-11 12:00:00+00'::timestamptz) < 2.0
AS new_moon_young;
SELECT moon_age('2024-01-25 18:00:00+00'::timestamptz)
BETWEEN 12.0 AND 17.0
AS full_moon_age_midcycle;
-- ============================================================
-- Illumination range: always [0, 1]
-- ============================================================
SELECT moon_illumination('2024-06-01 00:00:00+00'::timestamptz) BETWEEN 0.0 AND 1.0
AND moon_illumination('2024-09-01 00:00:00+00'::timestamptz) BETWEEN 0.0 AND 1.0
AND moon_illumination('2024-12-01 00:00:00+00'::timestamptz) BETWEEN 0.0 AND 1.0
AS illumination_always_valid;
-- ============================================================
-- Planet magnitude: Jupiter should be bright (negative mag)
-- ============================================================
SELECT planet_magnitude(5, '2024-01-15 00:00:00+00'::timestamptz) < 0.0
AS jupiter_is_bright;
-- ============================================================
-- Planet magnitude: Venus is the brightest planet
-- ============================================================
SELECT planet_magnitude(2, '2024-06-01 12:00:00+00'::timestamptz)
< planet_magnitude(4, '2024-06-01 12:00:00+00'::timestamptz)
AS venus_brighter_than_mars;
-- ============================================================
-- Planet magnitude: Neptune is faint (~+7-8)
-- ============================================================
SELECT planet_magnitude(8, '2024-01-15 00:00:00+00'::timestamptz) > 7.0
AS neptune_is_faint;
-- ============================================================
-- Planet magnitude: all planets return finite values
-- ============================================================
SELECT bool_and(
planet_magnitude(body_id, '2024-01-15 00:00:00+00'::timestamptz) IS NOT NULL
AND planet_magnitude(body_id, '2024-01-15 00:00:00+00'::timestamptz) > -30
AND planet_magnitude(body_id, '2024-01-15 00:00:00+00'::timestamptz) < 30
) AS all_magnitudes_finite
FROM (VALUES (1),(2),(4),(5),(6),(7),(8)) AS t(body_id);
-- ============================================================
-- Planet magnitude: error cases
-- ============================================================
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(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(9, '2024-01-15 00:00:00+00'::timestamptz); EXCEPTION WHEN OTHERS THEN RAISE NOTICE 'body_id=9: %', SQLERRM; END $$;