Generic search for rises, sets and meridian transits.
Why a numeric search rather than a closed-form formula
The classic hour-angle formula (cos H = (sin h₀ − sin φ sin δ) / (cos φ cos δ)) assumes δ constant over the day. That is acceptable for
the Sun, where it holds to the minute. For the Moon it is wrong: its
right ascension advances up to 15°/day and its declination up to 5°/day.
A closed-form moonrise can be off by more than a quarter of an hour.
So we sample the body's altitude hour by hour, spot sign changes of
altitude − h₀, then refine by bisection. The same code serves the Sun,
the Moon and the twilights — you only provide a function
jd_ut -> {α, δ, h₀}, and the result's accuracy is then limited only by
the underlying ephemeris.
Mind the time scale
body_fun receives a Julian day in UT. Applying ΔT before calling
the ephemeris series is its responsibility. Sidereal time, on the other
hand, is computed here from the received UT — as it must be.
Summary
Types
Function describing the observed body: receives a UT Julian day, returns
right ascension, declination (degrees) and the reference altitude h₀ of
the searched phenomenon (degrees).
A horizon or meridian event.
Result of a search.
Functions
Geometric altitude of a body above the horizon, in degrees.
Azimuth of a body, in degrees from North through East.
Local hour angle in degrees, reduced into [−180, 180).
Searches for events of a body between two UT Julian days.
Types
Function describing the observed body: receives a UT Julian day, returns
right ascension, declination (degrees) and the reference altitude h₀ of
the searched phenomenon (degrees).
@type event() :: %{ type: :rise | :set | :transit, jd: float(), azimuth: float(), altitude: float() }
A horizon or meridian event.
@type result() :: %{state: :normal | :always_above | :always_below, events: [event()]}
Result of a search.
:state is :normal, or :always_above / :always_below when the body
never crosses the horizon over the window: midnight sun, polar night, a
twilight that does not occur, or simply a Moon that does not rise that
day (which happens about one day in 27).
Functions
Geometric altitude of a body above the horizon, in degrees.
Azimuth of a body, in degrees from North through East.
Local hour angle in degrees, reduced into [−180, 180).
Negative before meridian transit, positive after.
Searches for events of a body between two UT Julian days.
Crossings shorter than the sampling step are still caught: the altitude can only peak at a culmination, so every sampled interval that contains one is probed at the culmination itself before being declared crossing-free. A 55-minute polar day between two hourly samples is found, not skipped.
Options:
:step— sampling step in days (default1/24, i.e. 1 h).:tolerance— bisection precision in days (default1.0e-7, i.e. ≈ 9 ms).:transits— include meridian transits (defaulttrue).