///|
/// Asking a station for the wind and temperature at an altitude it did not
/// forecast.
///
/// A bulletin forecasts a handful of levels; a flight plan wants the wind at
/// the altitude it will fly, which usually falls between two of them. Both
/// are interpolated from the levels that bracket the altitude, and answered
/// with `None` outside the levels the station reports — a forecast is not
/// extrapolated beyond what was forecast.
///|
/// The altitudes this station reports, in feet, in the order the bulletin
/// lists them, which is ascending.
///
/// A station does not necessarily report every level of the product: a column
/// the bulletin leaves blank is simply absent.
pub fn StationWinds::altitudes(self : StationWinds) -> Array[Int32] {
let out : Array[Int32] = []
for level in self.levels {
out.push(level.altitude)
}
out
}
///|
/// The lowest altitude this station reports, in feet.
pub fn StationWinds::lowest_altitude(self : StationWinds) -> Int32? {
if self.levels.length() == 0 {
None
} else {
Some(self.levels[0].altitude)
}
}
///|
/// The highest altitude this station reports, in feet.
pub fn StationWinds::highest_altitude(self : StationWinds) -> Int32? {
if self.levels.length() == 0 {
None
} else {
Some(self.levels[self.levels.length() - 1].altitude)
}
}
///|
/// The wind at `altitude` feet, as a vector in knots.
///
/// A reported level is returned as it was reported. An altitude between two
/// reported levels is interpolated between them, component by component; a
/// light and variable level counts as no wind at all. `None` when the station
/// reports nothing on both sides of `altitude`.
pub fn StationWinds::vector_at(
self : StationWinds,
altitude : Int32,
) -> WindVector? {
match self.level_at(altitude) {
Some(level) => return Some(WindVector::from_wind(level.wind))
None => ()
}
match self.bracketing_levels(altitude, fn(_) { true }) {
(Some(lower), Some(upper)) =>
Some(
WindVector::interpolate(
WindVector::from_wind(lower.wind),
WindVector::from_wind(upper.wind),
fraction_between(altitude, lower.altitude, upper.altitude),
),
)
_ => None
}
}
///|
/// The temperature at `altitude` feet, in degrees Celsius, interpolated
/// between the levels that forecast one.
///
/// `None` when no level on both sides of `altitude` carries a temperature,
/// which is the case at 3,000 ft, where the product never forecasts one.
pub fn StationWinds::temperature_at(
self : StationWinds,
altitude : Int32,
) -> Double? {
match self.level_at(altitude) {
Some(level) =>
match level.temperature {
Some(temperature) => return Some(temperature.to_double())
None => ()
}
None => ()
}
match
self.bracketing_levels(altitude, fn(level) { level.temperature is Some(_) }) {
(Some(lower), Some(upper)) =>
match (lower.temperature, upper.temperature) {
(Some(low), Some(high)) => {
let low = low.to_double()
let high = high.to_double()
Some(
low +
(high - low) *
fraction_between(altitude, lower.altitude, upper.altitude),
)
}
_ => None
}
_ => None
}
}
///|
/// The reported levels that bracket `altitude`: the highest one below it and
/// the lowest one above it.
///
/// `usable` picks the levels to consider, so that a temperature is
/// interpolated between the levels that carry one.
fn StationWinds::bracketing_levels(
self : StationWinds,
altitude : Int32,
usable : (Level) -> Bool,
) -> (Level?, Level?) {
let mut lower : Level? = None
let mut upper : Level? = None
for level in self.levels {
if !usable(level) {
continue
}
if level.altitude < altitude {
// The levels are ascending, so the last one below is the highest.
lower = Some(level)
} else if level.altitude > altitude && upper is None {
upper = Some(level)
}
}
(lower, upper)
}
///|
/// Where `altitude` falls between `low` and `high`, as a fraction from 0 to 1.
fn fraction_between(altitude : Int32, low : Int32, high : Int32) -> Double {
(altitude - low).to_double() / (high - low).to_double()
}