GreenCal.Astro.RiseSet (GreenCal v0.2.0)

Copy Markdown View Source

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

body_fun()

@type body_fun() :: (float() -> {float(), float(), float()})

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).

event()

@type event() :: %{
  type: :rise | :set | :transit,
  jd: float(),
  azimuth: float(),
  altitude: float()
}

A horizon or meridian event.

result()

@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

altitude(jd, lat, lon, ra, dec)

@spec altitude(float(), number(), number(), float(), float()) :: float()

Geometric altitude of a body above the horizon, in degrees.

azimuth(jd, lat, lon, ra, dec)

@spec azimuth(float(), number(), number(), float(), float()) :: float()

Azimuth of a body, in degrees from North through East.

hour_angle(jd, lon, ra)

@spec hour_angle(float(), number(), float()) :: float()

Local hour angle in degrees, reduced into [−180, 180).

Negative before meridian transit, positive after.

search(body_fun, arg, jd_from, jd_to, opts \\ [])

@spec search(body_fun(), {number(), number()}, float(), float(), keyword()) ::
  result()

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 (default 1/24, i.e. 1 h).
  • :tolerance — bisection precision in days (default 1.0e-7, i.e. ≈ 9 ms).
  • :transits — include meridian transits (default true).