use alloc::vec::Vec;
use super::{to_epoch, Series};
use crate::Point;
const SOURCE: &str = "isd-lite";
const MISSING: i64 = -9999;
struct Variable {
name: &'static str,
unit: &'static str,
column: usize,
divisor: f64,
}
const VARIABLES: [Variable; 5] = [
Variable {
name: "air_temperature",
unit: "degC",
column: 4,
divisor: 10.0,
},
Variable {
name: "dew_point",
unit: "degC",
column: 5,
divisor: 10.0,
},
Variable {
name: "sea_level_pressure",
unit: "hPa",
column: 6,
divisor: 10.0,
},
Variable {
name: "wind_direction",
unit: "deg",
column: 7,
divisor: 1.0,
},
Variable {
name: "wind_speed",
unit: "m/s",
column: 8,
divisor: 10.0,
},
];
pub fn parse(text: &str) -> Vec<Series> {
let mut series: Vec<Series> = VARIABLES
.iter()
.map(|v| Series {
source: SOURCE,
variable: v.name,
unit: v.unit,
points: Vec::new(),
})
.collect();
for line in text.lines() {
let fields: Vec<i64> = line
.split_whitespace()
.filter_map(|f| f.parse::<i64>().ok())
.collect();
if fields.len() < 9 {
continue;
}
let timestamp = to_epoch(fields[0], fields[1] as u32, fields[2] as u32, fields[3]);
for (index, variable) in VARIABLES.iter().enumerate() {
let raw = fields[variable.column];
if raw != MISSING {
series[index]
.points
.push(Point::new(timestamp, raw as f64 / variable.divisor));
}
}
}
series.retain(|s| !s.points.is_empty());
series
}
#[cfg(test)]
mod tests {
use super::*;
const SAMPLE: &str = "\
2023 01 01 00 82 49 10241 150 10 -9999 -9999 0
2023 01 01 01 78 39 10237 80 10 -9999 -9999 -9999
2023 01 01 02 -9999 36 10236 80 15 -9999 -9999 -9999
";
#[test]
fn tenths_become_the_values_a_station_actually_reports() {
let series = parse(SAMPLE);
let temperature = series
.iter()
.find(|s| s.variable == "air_temperature")
.unwrap();
assert_eq!(temperature.points[0].value, 8.2);
assert_eq!(temperature.points[1].value, 7.8);
}
#[test]
fn missing_readings_leave_a_gap_instead_of_a_value() {
let series = parse(SAMPLE);
let temperature = series
.iter()
.find(|s| s.variable == "air_temperature")
.unwrap();
let pressure = series
.iter()
.find(|s| s.variable == "sea_level_pressure")
.unwrap();
assert_eq!(temperature.points.len(), 2);
assert_eq!(pressure.points.len(), 3);
assert_eq!(
pressure.points[2].timestamp - pressure.points[1].timestamp,
3_600
);
}
#[test]
fn short_or_junk_lines_are_skipped() {
assert!(parse("not a row\n\n2023 01\n").is_empty());
}
}