use crate::error::*;
use chrono::{NaiveDate, NaiveDateTime};
use metfor::{Celsius, HectoPascal, Kelvin, Km, Knots, MetersPSec, Mm, WindSpdDir, WindUV};
use optional::{none, some, Optioned};
use std::error::Error;
#[derive(Debug, PartialEq)]
pub struct SurfaceData {
pub station_num: i32, pub valid_time: NaiveDateTime, pub mslp: Optioned<HectoPascal>, pub station_pres: Optioned<HectoPascal>, pub low_cloud: Optioned<f64>, pub mid_cloud: Optioned<f64>, pub hi_cloud: Optioned<f64>, pub wind: Optioned<WindSpdDir<Knots>>, pub temperature: Optioned<Celsius>, pub dewpoint: Optioned<Celsius>,
pub skin_temp: Optioned<Celsius>, pub lyr_1_soil_temp: Optioned<Kelvin>, pub snow_1hr: Optioned<f64>,
pub p01: Optioned<Mm>, pub c01: Optioned<Mm>, pub lyr_2_soil_temp: Optioned<Kelvin>, pub snow_ratio: Optioned<f64>, pub snow_type: Option<bool>, pub ice_pellets_type: Option<bool>, pub fzra_type: Option<bool>, pub rain_type: Option<bool>, pub storm_motion: Optioned<WindUV<MetersPSec>>, pub srh: Optioned<f64>, pub wx_sym_cod: Optioned<f64>, pub visibility: Optioned<Km>, }
impl SurfaceData {
pub fn parse_columns(header: &str) -> Result<SfcColumns, BufkitFileError> {
use self::SfcColName::*;
let cols_text = header.trim().split_whitespace();
let mut cols = SfcColumns {
names: Vec::with_capacity(33),
};
for val in cols_text {
match val.trim() {
"STN" => cols.names.push(STN),
"YYMMDD/HHMM" => cols.names.push(VALIDTIME),
"PMSL" => cols.names.push(PMSL),
"PRES" => cols.names.push(PRES),
"LCLD" => cols.names.push(LCLD),
"MCLD" => cols.names.push(MCLD),
"HCLD" => cols.names.push(HCLD),
"UWND" => cols.names.push(UWND),
"VWND" => cols.names.push(VWND),
"T2MS" => cols.names.push(T2MS),
"TD2M" => cols.names.push(TD2M),
"SKTC" => cols.names.push(SKTC),
"STC1" => cols.names.push(STC1),
"SNFL" => cols.names.push(SNFL),
"P01M" => cols.names.push(P01M),
"C01M" => cols.names.push(C01M),
"STC2" => cols.names.push(STC2),
"SNRA" => cols.names.push(SNRA),
"WXTS" => cols.names.push(WXTS),
"WXTP" => cols.names.push(WXTP),
"WXTZ" => cols.names.push(WXTZ),
"WXTR" => cols.names.push(WXTR),
"USTM" => cols.names.push(USTM),
"VSTM" => cols.names.push(VSTM),
"HLCY" => cols.names.push(HLCY),
"WSYM" => cols.names.push(WSYM),
"VSBK" => cols.names.push(VSBK),
_ => cols.names.push(NONE),
}
}
{
let names: &Vec<_> = &cols.names;
if names.iter().find(|&&x| x == STN).is_none()
|| names.iter().find(|&&x| x == VALIDTIME).is_none()
{
return Err(BufkitFileError::new());
}
}
Ok(cols)
}
pub fn parse_values(tokens: &str, cols: &SfcColumns) -> Result<SurfaceData, Box<dyn Error>> {
use std::str::FromStr;
let mut tokens = tokens.split_whitespace();
let mut sd = SurfaceData::default();
let mut u_wind: Optioned<MetersPSec> = none();
let mut v_wind: Optioned<MetersPSec> = none();
let mut u_storm: Optioned<MetersPSec> = none();
let mut v_storm: Optioned<MetersPSec> = none();
for i in 0..cols.num_cols() {
if let Some(token) = tokens.next() {
use self::SfcColName::*;
use crate::parse_util::*;
let _dummy: f64;
match cols.names[i] {
NONE => _dummy = f64::from_str(token)?,
STN => sd.station_num = i32::from_str(token)?,
VALIDTIME => sd.valid_time = parse_naive_date_time(token)?,
PMSL => sd.mslp = check_missing(f64::from_str(token)?).map_t(HectoPascal),
PRES => {
sd.station_pres = check_missing(f64::from_str(token)?).map_t(HectoPascal)
}
LCLD => {
sd.low_cloud = check_missing(f64::from_str(token)?).map_t(|val| val / 100.0)
}
MCLD => {
sd.mid_cloud = check_missing(f64::from_str(token)?).map_t(|val| val / 100.0)
}
HCLD => {
sd.hi_cloud = check_missing(f64::from_str(token)?).map_t(|val| val / 100.0)
}
UWND => u_wind = check_missing(f64::from_str(token)?).map_t(MetersPSec),
VWND => v_wind = check_missing(f64::from_str(token)?).map_t(MetersPSec),
T2MS => sd.temperature = check_missing(f64::from_str(token)?).map_t(Celsius),
TD2M => sd.dewpoint = check_missing(f64::from_str(token)?).map_t(Celsius),
SKTC => sd.skin_temp = check_missing(f64::from_str(token)?).map_t(Celsius),
STC1 => sd.lyr_1_soil_temp = check_missing(f64::from_str(token)?).map_t(Kelvin),
SNFL => sd.snow_1hr = check_missing(f64::from_str(token)?),
P01M => sd.p01 = check_missing(f64::from_str(token)?).map_t(Mm),
C01M => sd.c01 = check_missing(f64::from_str(token)?).map_t(Mm),
STC2 => sd.lyr_2_soil_temp = check_missing(f64::from_str(token)?).map_t(Kelvin),
SNRA => sd.snow_ratio = check_missing(f64::from_str(token)?),
WXTS => {
sd.snow_type = check_missing(f64::from_str(token)?).map(|val| val > 0.5)
}
WXTP => {
sd.ice_pellets_type =
check_missing(f64::from_str(token)?).map(|val| val > 0.5)
}
WXTZ => {
sd.fzra_type = check_missing(f64::from_str(token)?).map(|val| val > 0.5)
}
WXTR => {
sd.rain_type = check_missing(f64::from_str(token)?).map(|val| val > 0.5)
}
USTM => u_storm = check_missing(f64::from_str(token)?).map_t(MetersPSec),
VSTM => v_storm = check_missing(f64::from_str(token)?).map_t(MetersPSec),
HLCY => sd.srh = check_missing(f64::from_str(token)?),
WSYM => {
sd.wx_sym_cod = if let Ok(val) = f64::from_str(token) {
if val == MISSING_F64_INDEX || val == MISSING_F64 {
none()
} else {
some(val)
}
} else {
none()
}
}
VSBK => sd.visibility = check_missing(f64::from_str(token)?).map_t(Km),
};
} else {
return Err(BufkitFileError::new().into());
}
}
sd.wind = u_wind.and_then(|u| v_wind.map_t(|v| WindSpdDir::<Knots>::from(WindUV { u, v })));
sd.storm_motion = u_storm.and_then(|u| v_storm.map_t(|v| WindUV { u, v }));
Ok(sd)
}
}
impl Default for SurfaceData {
fn default() -> SurfaceData {
SurfaceData {
station_num: ::std::i32::MIN,
valid_time: NaiveDate::from_ymd_opt(0, 1, 1)
.unwrap()
.and_hms_opt(0, 0, 0)
.unwrap(),
mslp: none(),
station_pres: none(),
low_cloud: none(),
mid_cloud: none(),
hi_cloud: none(),
wind: none(),
temperature: none(),
dewpoint: none(),
skin_temp: none(),
lyr_1_soil_temp: none(),
snow_1hr: none(),
p01: none(),
c01: none(),
lyr_2_soil_temp: none(),
snow_ratio: none(),
ice_pellets_type: None,
snow_type: None,
fzra_type: None,
rain_type: None,
storm_motion: none(),
srh: none(),
wx_sym_cod: none(),
visibility: none(),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum SfcColName {
NONE,
STN,
VALIDTIME,
PMSL, PRES, LCLD, MCLD, HCLD, UWND, VWND, T2MS, TD2M, SKTC, STC1, SNFL, P01M, C01M, STC2, SNRA, WXTS, WXTP, WXTZ, WXTR, USTM, VSTM, HLCY, WSYM, VSBK, }
#[derive(Debug)]
pub struct SfcColumns {
names: Vec<SfcColName>,
}
impl SfcColumns {
pub fn num_cols(&self) -> usize {
self.names.len()
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_parse_columns() {
use self::SfcColName::*;
let test_data = "STN YYMMDD/HHMM PMSL PRES SKTC STC1 EVAP P03M C03M SWEM LCLD MCLD HCLD \
UWND VWND T2MS Q2MS WXTS WXTP WXTZ WXTR S03M TD2M ";
let col_idx = SurfaceData::parse_columns(test_data).unwrap();
assert_eq!(col_idx.num_cols(), 23);
for i in 1..col_idx.names.len() {
let col_name: SfcColName;
match i {
0 => col_name = STN,
1 => col_name = VALIDTIME,
2 => col_name = PMSL,
3 => col_name = PRES,
4 => col_name = SKTC,
5 => col_name = STC1,
10 => col_name = LCLD,
11 => col_name = MCLD,
12 => col_name = HCLD,
13 => col_name = UWND,
14 => col_name = VWND,
15 => col_name = T2MS,
17 => col_name = WXTS,
18 => col_name = WXTP,
19 => col_name = WXTZ,
20 => col_name = WXTR,
22 => col_name = TD2M,
_ => col_name = NONE,
};
assert_eq!(col_idx.names[i], col_name);
}
let test_data = "STN YYMMDD/HHMM PMSL PRES SKTC STC1 SNFL WTNS P01M C01M STC2 LCLD MCLD \
HCLD SNRA UWND VWND R01M BFGR T2MS Q2MS WXTS WXTP WXTZ WXTR USTM VSTM \
HLCY SLLH WSYM CDBP VSBK TD2M ";
let col_idx = SurfaceData::parse_columns(test_data).unwrap();
assert_eq!(col_idx.num_cols(), 33);
for i in 1..col_idx.names.len() {
let col_name: SfcColName;
match i {
0 => col_name = STN,
1 => col_name = VALIDTIME,
2 => col_name = PMSL,
3 => col_name = PRES,
4 => col_name = SKTC,
5 => col_name = STC1,
6 => col_name = SNFL,
8 => col_name = P01M,
9 => col_name = C01M,
10 => col_name = STC2,
11 => col_name = LCLD,
12 => col_name = MCLD,
13 => col_name = HCLD,
14 => col_name = SNRA,
15 => col_name = UWND,
16 => col_name = VWND,
19 => col_name = T2MS,
21 => col_name = WXTS,
22 => col_name = WXTP,
23 => col_name = WXTZ,
24 => col_name = WXTR,
25 => col_name = USTM,
26 => col_name = VSTM,
27 => col_name = HLCY,
29 => col_name = WSYM,
31 => col_name = VSBK,
32 => col_name = TD2M,
_ => col_name = NONE,
};
assert_eq!(col_idx.names[i], col_name);
}
let test_data = "STN PMSL PRES SKTC STC1 SNFL WTNS P01M C01M STC2 LCLD MCLD HCLD SNRA \
UWND VWND R01M BFGR T2MS Q2MS WXTS WXTP WXTZ WXTR USTM VSTM HLCY SLLH \
WSYM CDBP VSBK TD2M ";
assert!(SurfaceData::parse_columns(test_data).is_err());
}
}