Merge pull request #67 from outis1one/claude/fix-sunrise-sunset-observer-BA0Iy
Claude/fix sunrise sunset observer ba0 iy
This commit is contained in:
@@ -295,8 +295,16 @@ Replace the date list with whatever range you need. Each run produces one `*-fi
|
|||||||
# Inspect any moon-related stats for a frame
|
# Inspect any moon-related stats for a frame
|
||||||
python3 moon_detect.py BASE_DIR/east/2026-04-29/21-07-00.jpg --debug /tmp/dbg.png
|
python3 moon_detect.py BASE_DIR/east/2026-04-29/21-07-00.jpg --debug /tmp/dbg.png
|
||||||
python3 moon_phase.py info 2026-04-29T21:07:00Z
|
python3 moon_phase.py info 2026-04-29T21:07:00Z
|
||||||
|
|
||||||
|
# Smoke-test moon ephemeris + sunrise/sunset almanac against the last full moon
|
||||||
|
python3 test_last_full_moon.py
|
||||||
```
|
```
|
||||||
|
|
||||||
|
`test_last_full_moon.py` finds the most recent full moon, prints illumination, phase angle,
|
||||||
|
altitude/azimuth, and parallactic angle, then lists the sunrise and sunset for that day.
|
||||||
|
It exercises the full skyfield stack including the `sun_events_in_range()` almanac call —
|
||||||
|
useful after updating skyfield or changing observer config.
|
||||||
|
|
||||||
**Posting schedule**:
|
**Posting schedule**:
|
||||||
|
|
||||||
| Job | When the timer fires | When the artifact actually appears |
|
| Job | When the timer fires | When the artifact actually appears |
|
||||||
|
|||||||
+5
-3
@@ -67,6 +67,7 @@ _EPH_PATH = _here / 'de421.bsp'
|
|||||||
_ts = None
|
_ts = None
|
||||||
_eph = None
|
_eph = None
|
||||||
_observer = None
|
_observer = None
|
||||||
|
_topos = None # bare geographic position; sunrise_sunset() adds earth itself
|
||||||
|
|
||||||
|
|
||||||
def _lazy():
|
def _lazy():
|
||||||
@@ -75,7 +76,7 @@ def _lazy():
|
|||||||
Lets the module be imported by tests and by --help paths even when
|
Lets the module be imported by tests and by --help paths even when
|
||||||
skyfield is missing or the ephemeris hasn't been downloaded yet.
|
skyfield is missing or the ephemeris hasn't been downloaded yet.
|
||||||
"""
|
"""
|
||||||
global _ts, _eph, _observer
|
global _ts, _eph, _observer, _topos
|
||||||
if _ts is not None:
|
if _ts is not None:
|
||||||
return
|
return
|
||||||
from skyfield.api import Loader, wgs84
|
from skyfield.api import Loader, wgs84
|
||||||
@@ -85,7 +86,8 @@ def _lazy():
|
|||||||
_eph = loader('de421.bsp')
|
_eph = loader('de421.bsp')
|
||||||
else:
|
else:
|
||||||
_eph = loader('de421.bsp') # downloads on first run
|
_eph = loader('de421.bsp') # downloads on first run
|
||||||
_observer = _eph['earth'] + wgs84.latlon(LATITUDE, LONGITUDE)
|
_topos = wgs84.latlon(LATITUDE, LONGITUDE)
|
||||||
|
_observer = _eph['earth'] + _topos
|
||||||
|
|
||||||
|
|
||||||
def _to_utc(when: datetime) -> datetime:
|
def _to_utc(when: datetime) -> datetime:
|
||||||
@@ -192,7 +194,7 @@ def sun_events_in_range(
|
|||||||
from skyfield.almanac import find_discrete, sunrise_sunset
|
from skyfield.almanac import find_discrete, sunrise_sunset
|
||||||
t0 = _t(search_start)
|
t0 = _t(search_start)
|
||||||
t1 = _t(search_end)
|
t1 = _t(search_end)
|
||||||
times, values = find_discrete(t0, t1, sunrise_sunset(_eph, _observer))
|
times, values = find_discrete(t0, t1, sunrise_sunset(_eph, _topos))
|
||||||
return [(t.utc_datetime(), bool(v)) for t, v in zip(times, values)]
|
return [(t.utc_datetime(), bool(v)) for t, v in zip(times, values)]
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""test_last_full_moon.py — smoke-test moon_phase.py against the most recent full moon.
|
||||||
|
|
||||||
|
Exercises:
|
||||||
|
- full_moons_in_range() — phase detection
|
||||||
|
- sun_events_in_range() — sunrise/sunset almanac (requires bare topos, not earth+topos)
|
||||||
|
- illumination() / altaz() / parallactic_angle()
|
||||||
|
|
||||||
|
Run from the sky-cam directory:
|
||||||
|
|
||||||
|
python3 test_last_full_moon.py
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import pathlib
|
||||||
|
from datetime import datetime, timedelta, timezone
|
||||||
|
|
||||||
|
HERE = pathlib.Path(__file__).resolve().parent
|
||||||
|
sys.path.insert(0, str(HERE))
|
||||||
|
|
||||||
|
import moon_phase as mp
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
now = datetime.now(timezone.utc)
|
||||||
|
|
||||||
|
# Last full moon: search back up to 35 days
|
||||||
|
candidates = mp.full_moons_in_range(now - timedelta(days=35), now)
|
||||||
|
if not candidates:
|
||||||
|
print("ERROR: no full moon found in the last 35 days", file=sys.stderr)
|
||||||
|
return 1
|
||||||
|
fm = candidates[-1]
|
||||||
|
|
||||||
|
print(f"last full moon (UTC) : {fm.strftime('%Y-%m-%dT%H:%M:%SZ')}")
|
||||||
|
print(f"local : {mp._format_local(fm)}")
|
||||||
|
|
||||||
|
# Moon stats at exact full moon moment
|
||||||
|
illum = mp.illumination(fm)
|
||||||
|
pa = mp.phase_angle(fm)
|
||||||
|
alt, az = mp.altaz(fm)
|
||||||
|
para = mp.parallactic_angle(fm)
|
||||||
|
print(f"illumination : {illum:.4f} ({illum*100:.1f}%)")
|
||||||
|
print(f"phase angle : {pa:.2f}° (0° = full)")
|
||||||
|
print(f"altitude / azimuth : {alt:.2f}° / {az:.2f}°")
|
||||||
|
print(f"parallactic angle : {para:.2f}°")
|
||||||
|
print()
|
||||||
|
|
||||||
|
# Sunrise/sunset for the 24-hour window around the full moon — exercises the fix
|
||||||
|
day_start = fm.replace(hour=0, minute=0, second=0, microsecond=0)
|
||||||
|
day_end = day_start + timedelta(days=1)
|
||||||
|
events = mp.sun_events_in_range(day_start, day_end)
|
||||||
|
if events:
|
||||||
|
print(f"sun events on {day_start.strftime('%Y-%m-%d')} (UTC):")
|
||||||
|
for t, is_rise in events:
|
||||||
|
label = "sunrise" if is_rise else "sunset "
|
||||||
|
print(f" {label} {t.strftime('%H:%M:%S')} UTC ({mp._format_local(t)})")
|
||||||
|
else:
|
||||||
|
print(f"no sun events found on {day_start.strftime('%Y-%m-%d')} (polar location?)")
|
||||||
|
|
||||||
|
print()
|
||||||
|
print("OK")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
Reference in New Issue
Block a user