// SunCalc 2.0.1 Meeus ch.15 solvers. See THIRD_PARTY.md.
///|
pub(all) enum HorizonState {
Crosses
AlwaysAbove
AlwaysBelow
} derive(Eq, Debug)
///|
pub(all) struct SolarEvent {
angle : Double
rise_name : String
set_name : String
rise : Instant?
set : Instant?
state : HorizonState
} derive(Eq, Debug)
///|
pub(all) struct SunTimes {
solar_noon : Instant
nadir : Instant
events : Array[SolarEvent]
state : HorizonState
} derive(Eq, Debug)
///|
fn wrap_pi(a : Double) -> Double {
a - 2.0 * @math.PI * js_round(a / (2.0 * @math.PI))
}
///|
fn solar_transit(start : Double, lw : Double) -> Double {
let mut dt = start
for i = 0; i < 3; i = i + 1 {
let h = wrap_pi(sidereal_time(dt, lw) - sun_coords(to_tt(dt)).ra)
dt -= h / (2.0 * @math.PI)
}
dt
}
///|
fn solar_crossing(
h0 : Double,
dt : Double,
sign : Double,
lw : Double,
phi : Double,
dec_t : Double,
) -> Double? {
let cos_h0 = (@math.sin(h0) - @math.sin(phi) * @math.sin(dec_t)) /
(@math.cos(phi) * @math.cos(dec_t))
if cos_h0 < -1.0 || cos_h0 > 1.0 {
return None
}
let mut d = dt + sign * @math.acos(cos_h0) / (2.0 * @math.PI)
for i = 0; i < 2; i = i + 1 {
let c = sun_coords(to_tt(d))
let h = wrap_pi(sidereal_time(d, lw) - c.ra)
let alt = altitude(h, phi, c.dec)
let sin_h = @math.cos(phi) * @math.cos(c.dec) * @math.sin(h)
if sin_h.abs() < 0.000001 {
break
}
d += (alt - h0) / (2.0 * @math.PI * sin_h)
}
Some(d)
}
///|
/// Six standard event pairs. Missing polar events are None, never invalid dates.
pub fn sun_times_with(
date : Instant,
site : Observer,
config : SolarConfig,
) -> SunTimes {
let lw = Rad * -site.lng
let phi = Rad * site.lat
let d = js_round(js_round(to_days(date)) - 0.0009 - lw / (2.0 * @math.PI))
let dt = solar_transit(d + 0.0009 + lw / (2.0 * @math.PI), lw)
let dec = sun_coords(to_tt(dt)).dec
let events = []
let configured = standard_angles()
for extra in config.extra {
configured.push((extra.angle, extra.rise_name, extra.set_name))
}
let dh = -2.076 * config.height.sqrt() / 60.0
for entry in configured {
let (angle, rise_name, set_name) = entry
let rise = solar_crossing((angle + dh) * Rad, dt, -1.0, lw, phi, dec).map(
from_days,
)
let set = solar_crossing((angle + dh) * Rad, dt, 1.0, lw, phi, dec).map(
from_days,
)
let state = if rise is Some(_) {
Crosses
} else if altitude(0.0, phi, dec) > (angle + dh) * Rad {
AlwaysAbove
} else {
AlwaysBelow
}
events.push({ angle, rise_name, set_name, rise, set, state, })
}
{
solar_noon: from_days(dt),
nadir: from_days(dt - 0.5),
events,
state: events[0].state,
}
}
///|
/// Lookup a standard or configured event by its case-sensitive upstream name.
pub fn SunTimes::event(self : SunTimes, name : String) -> Instant? {
for e in self.events {
if e.rise_name == name {
return e.rise
}
if e.set_name == name {
return e.set
}
}
None
}
///|
/// Six standard pairs at sea-level horizon.
pub fn sun_times(date : Instant, site : Observer) -> SunTimes {
sun_times_with(date, site, { height: 0.0, extra: [], })
}