///|
/// Errors raised while reading or writing winds-aloft (FD) bulletins.
pub suberror FdError {
/// A required header line (the `FT` column header) is missing.
MissingLevelHeader
/// The `FT` header lists no usable levels.
BadLevelHeader(String)
/// A station line is too short or its identifier is malformed.
BadStationLine(String)
/// A wind group is not four digits, or decodes outside the valid range.
BadWindGroup(String)
/// A temperature field is malformed.
BadTemperature(String)
}
///|
/// One upper-air wind: the direction the wind blows *from*, in degrees true,
/// and its speed in knots.
///
/// `direction` is `None` for a light and variable wind (encoded `9900`),
/// which is a wind below 5 knots with no useful direction. Directions are
/// always multiples of 10 degrees, as the wire format carries tens of
/// degrees.
pub struct Wind {
direction : Int32?
speed : Int32
}
///|
/// One forecast level of a station: the altitude in feet above mean sea
/// level, the wind, and the temperature in degrees Celsius. `temperature` is
/// `None` when the product does not forecast one, which is always the case at
/// 3,000 ft and at levels close to the station elevation.
pub struct Level {
altitude : Int32
wind : Wind
temperature : Int32?
}
///|
/// The winds and temperatures aloft forecast for one station.
///
/// `levels` holds only the altitudes the product actually reports for this
/// station; a missing level is simply absent, exactly as a blank column in
/// the bulletin.
pub struct StationWinds {
station : String
levels : Array[Level]
}
///|
/// A parsed winds and temperatures aloft bulletin, such as the `FD1US1`
/// product.
///
/// The header lines are kept verbatim so that `format_bulletin` reproduces
/// the original text byte for byte.
pub struct Bulletin {
/// Every line before the `FT` column header, verbatim.
header : Array[String]
/// The `FT` column header line, verbatim.
level_header : String
/// Forecast levels in column order, in feet MSL, parsed from `level_header`.
altitudes : Array[Int32]
/// The WMO abbreviated heading, such as `FBUS31 KWNO 221358`.
wmo_header : String?
/// The product designator, such as `FD1US1`.
designator : String?
/// The `DATA BASED ON ...Z` observation time, when present.
based_on : String?
/// The `VALID ...Z` valid time, when present.
valid : String?
/// The `FOR USE ...Z` usable window, when present.
use_window : String?
/// The altitude above which temperatures are reported without a sign,
/// parsed from `TEMPS NEG ABV n`; 24,000 ft in current products.
temps_negative_above : Int32?
stations : Array[StationWinds]
/// `stations` by identifier, so that finding one station does not look at
/// the whole product.
index : @hashmap.HashMap[String, StationWinds]
/// Lines after the last station line, verbatim. Present in some products,
/// which end with a list of stations that were not received.
trailing : Array[String]
}
///|
/// A wind from the direction it blows from and its speed, which is how one is
/// reported: `Wind::new(Some(150), 14)` is 150 degrees at 14 kt, and
/// `Wind::new(None, 0)` is light and variable.
pub fn Wind::new(direction : Int32?, speed : Int32) -> Wind {
{ direction, speed, }
}
///|
/// Accessors.
pub fn Wind::direction(self : Wind) -> Int32? {
self.direction
}
///|
pub fn Wind::speed(self : Wind) -> Int32 {
self.speed
}
///|
/// Whether this wind is a light and variable report (`9900`).
pub fn Wind::is_light_and_variable(self : Wind) -> Bool {
self.direction is None && self.speed == 0
}
///|
pub fn Level::altitude(self : Level) -> Int32 {
self.altitude
}
///|
pub fn Level::wind(self : Level) -> Wind {
self.wind
}
///|
pub fn Level::temperature(self : Level) -> Int32? {
self.temperature
}
///|
pub fn StationWinds::station(self : StationWinds) -> String {
self.station
}
///|
pub fn StationWinds::levels(self : StationWinds) -> Array[Level] {
self.levels
}
///|
/// The level at exactly `altitude` feet, if the station reports one.
pub fn StationWinds::level_at(self : StationWinds, altitude : Int32) -> Level? {
for level in self.levels {
if level.altitude == altitude {
return Some(level)
}
}
None
}
///|
pub fn Bulletin::stations(self : Bulletin) -> Array[StationWinds] {
self.stations
}
///|
/// The levels the product forecasts, in feet MSL, as the `FT` header lists
/// them. Stations do not all report every one of them.
pub fn Bulletin::altitudes(self : Bulletin) -> Array[Int32] {
self.altitudes
}
///|
/// The WMO abbreviated heading of the product, such as `FBUS31 KWNO 221358`,
/// when it carries one.
pub fn Bulletin::wmo_header(self : Bulletin) -> String? {
self.wmo_header
}
///|
/// The product designator, such as `FD1US1`, when the product carries one.
pub fn Bulletin::designator(self : Bulletin) -> String? {
self.designator
}
///|
/// The observation time the forecast is based on, as the product writes it.
pub fn Bulletin::based_on(self : Bulletin) -> String? {
self.based_on
}
///|
/// The time the forecast is valid for, as the product writes it.
pub fn Bulletin::valid(self : Bulletin) -> String? {
self.valid
}
///|
/// The window the forecast is meant to be used in, as the product writes it.
pub fn Bulletin::use_window(self : Bulletin) -> String? {
self.use_window
}
///|
/// The altitude above which temperatures are reported without a sign, in feet
/// MSL, from `TEMPS NEG ABV n`; 24,000 ft in current products.
pub fn Bulletin::temps_negative_above(self : Bulletin) -> Int32? {
self.temps_negative_above
}
///|
/// The forecast for one station, by its three-character identifier.
pub fn Bulletin::station(self : Bulletin, id : String) -> StationWinds? {
self.index.get(id)
}