use crate::bufkit_data::surface::{SfcColumns, SurfaceData};
use crate::error::*;
use std::error::Error;
pub struct SurfaceSection<'a> {
raw_text: &'a str,
columns: SfcColumns,
}
impl<'a> SurfaceSection<'a> {
pub fn init(text: &'a str) -> Result<SurfaceSection<'a>, BufkitFileError> {
let mut header_end: usize = 0;
let mut previous_char = 'x';
let mut found = false;
for (i, c) in text.char_indices() {
header_end = i;
if previous_char.is_whitespace() && c.is_digit(10) {
found = true;
break;
} else {
previous_char = c;
}
}
if !found {
return Err(BufkitFileError::new());
}
let header = &text[0..header_end].trim();
let cols = SurfaceData::parse_columns(header)?;
Ok(SurfaceSection {
raw_text: text[header_end..].trim(),
columns: cols,
})
}
pub fn validate_section(&self) -> Result<(), Box<dyn Error>> {
let mut iter = self.into_iter();
loop {
let opt = iter.get_next_chunk()?;
if let Some(chunk) = opt {
SurfaceData::parse_values(chunk, iter.columns)?;
} else {
break;
}
}
Ok(())
}
}
impl<'a> IntoIterator for &'a SurfaceSection<'a> {
type Item = SurfaceData;
type IntoIter = SurfaceIterator<'a>;
fn into_iter(self) -> Self::IntoIter {
SurfaceIterator {
remaining: self.raw_text,
columns: &self.columns,
}
}
}
pub struct SurfaceIterator<'a> {
remaining: &'a str,
columns: &'a SfcColumns,
}
impl<'a> SurfaceIterator<'a> {
fn get_next_chunk(&mut self) -> Result<Option<&'a str>, BufkitFileError> {
use crate::parse_util::find_next_n_tokens;
if let Some(brk) = find_next_n_tokens(self.remaining, self.columns.num_cols())? {
let next_chunk = &self.remaining[0..brk];
self.remaining = &self.remaining[brk..];
Ok(Some(next_chunk))
} else {
Ok(None)
}
}
}
impl<'a> Iterator for SurfaceIterator<'a> {
type Item = SurfaceData;
fn next(&mut self) -> Option<SurfaceData> {
while let Ok(Some(text)) = self.get_next_chunk() {
if let Ok(sd) = SurfaceData::parse_values(text, self.columns) {
return Some(sd);
}
}
None
}
}
#[cfg(test)]
mod test {
use super::*;
fn get_valid_test_data() -> &'static str {
"
STN YYMMDD/HHMM PMSL PRES SKTC STC1 EVAP P03M
C03M SWEM LCLD MCLD HCLD UWND
VWND T2MS Q2MS WXTS WXTP WXTZ
WXTR S03M TD2M
727730 170401/0000 1020.40 909.10 10.54 278.70 -9999.00 0.00
0.00 0.00 100.00 0.00 58.00 0.90
-0.10 10.34 4.59 0.00 0.00 0.00
0.00 -9999.00 1.13
727730 170401/0300 1021.50 909.40 2.14 278.20 10.00 0.00
0.00 0.00 52.00 0.00 61.00 1.30
0.50 3.24 4.42 0.00 0.00 0.00
0.00 -9999.00 0.62
727730 170401/0600 1022.30 909.40 0.34 277.50 -9999.00 0.00
0.00 0.00 2.00 0.00 39.00 1.10
0.60 1.24 3.91 0.00 0.00 0.00
0.00 -9999.00 -1.06
727730 170401/0900 1022.70 909.30 -0.66 277.00 -9999.00 0.00
0.00 0.02 1.00 0.00 33.00 1.10
0.60 0.24 3.65 0.00 0.00 0.00
0.00 -9999.00 -1.99
727730 170401/1200 1022.60 908.80 -1.16 276.80 -9999.00 0.00
0.00 0.02 3.00 0.00 49.00 0.60
0.80 -0.56 3.50 0.00 0.00 0.00
0.00 -9999.00 -2.56
727730 170401/1500 1021.80 908.60 3.44 276.20 2.00 0.00
0.00 0.02 4.00 0.00 77.00 0.40
0.60 2.84 3.88 0.00 0.00 0.00
0.00 -9999.00 -1.17"
}
fn get_invalid_test_data1() -> &'static str {
"
STN YYMMDD/HHMM PMSL PRES SKTC STC1 EVAP P03M
C03M SWEM LCLD MCLD HCLD UWND
VWND T2MS Q2MS WXTS WXTP WXTZ
WXTR S03M TD2M
727730 170401/0000 1020.40 909.10 10.54 278.70 -9999.00 0.00
0.00 0.00 100.00 0.00 58.00 0.90
-0.10 10.34 4.59 0.00 0.00 0.00
0.00 -9999.00 1.13
727730 170401/0300 1021.50 909.40 2.14 278.20 10.00 0.00
0.00 0.00 52.00 0.00 61.00 1.30
0.50 3.24 4.42 0.00 0.00 0.00
0.00 -9999.00 0.62
727730 170401/0600 1022.30 909.40 0.34 277.50 -9999.00 0.00
0.00 0.00 2.00 0.00 39.00 1.10
0.60 1.24 3.91 0.00 0.00 0.00
0.00 -9999.00 -1.06
727730 170401/0900 1022.70 909.30 -0.66 277.00 -9999.00 0.00
0.00 0.02 1.00 0.00 33.00 1.10
0.60 0.24 3.65 0.00 0.00 0.00
0.00 -9999.00 -1.99
727730 170401/1200 1022.60 908.80 -1.16 276.80 -9999.00 0.00
0.00 0.02 3.00 0.00 49.00 0.60
0.80 -0.56 3.50 0.00 0.00 0.00
0.00 -9999.00 -2.56
727730 170401/1500 1021.80 908.60 3.44 276.20 2.00 0.00
0.00 0.02 4.00 0.00 77.00 0.40
0.60 2.84 3.88 0.00 0.00 0.00
0.00 -9999.00"
}
fn get_invalid_test_data2() -> &'static str {
"
STN PMSL PRES SKTC STC1 EVAP P03M
C03M SWEM LCLD MCLD HCLD UWND
VWND T2MS Q2MS WXTS WXTP WXTZ
WXTR S03M TD2M
727730 170401/0000 1020.40 909.10 10.54 278.70 -9999.00 0.00
0.00 0.00 100.00 0.00 58.00 0.90
-0.10 10.34 4.59 0.00 0.00 0.00
0.00 -9999.00 1.13
727730 170401/0300 1021.50 909.40 2.14 278.20 10.00 0.00
0.00 0.00 52.00 0.00 61.00 1.30
0.50 3.24 4.42 0.00 0.00 0.00
0.00 -9999.00 0.62
727730 170401/0600 1022.30 909.40 0.34 277.50 -9999.00 0.00
0.00 0.00 2.00 0.00 39.00 1.10
0.60 1.24 3.91 0.00 0.00 0.00
0.00 -9999.00 -1.06
727730 170401/0900 1022.70 909.30 -0.66 277.00 -9999.00 0.00
0.00 0.02 1.00 0.00 33.00 1.10
0.60 0.24 3.65 0.00 0.00 0.00
0.00 -9999.00 -1.99
727730 170401/1200 1022.60 908.80 -1.16 276.80 -9999.00 0.00
0.00 0.02 3.00 0.00 49.00 0.60
0.80 -0.56 3.50 0.00 0.00 0.00
0.00 -9999.00 -2.56
727730 170401/1500 1021.80 908.60 3.44 276.20 2.00 0.00
0.00 0.02 4.00 0.00 77.00 0.40
0.60 2.84 3.88 0.00 0.00 0.00
0.00 -9999.00 -1.17"
}
#[test]
fn test_surface_through_iterator() {
use chrono::{NaiveDate, NaiveDateTime};
use metfor::*;
use optional::some;
let test_data = get_valid_test_data();
let surface_section = SurfaceSection::init(test_data).unwrap();
assert_eq!(surface_section.into_iter().count(), 6);
for sd in &surface_section {
assert_eq!(sd.station_num, 727730);
}
assert_eq!(
surface_section
.into_iter()
.map(|sd| sd.valid_time)
.collect::<Vec<NaiveDateTime>>(),
vec![
NaiveDate::from_ymd_opt(2017, 4, 1).unwrap().and_hms_opt(0, 0, 0).unwrap(),
NaiveDate::from_ymd_opt(2017, 4, 1).unwrap().and_hms_opt(3, 0, 0).unwrap(),
NaiveDate::from_ymd_opt(2017, 4, 1).unwrap().and_hms_opt(6, 0, 0).unwrap(),
NaiveDate::from_ymd_opt(2017, 4, 1).unwrap().and_hms_opt(9, 0, 0).unwrap(),
NaiveDate::from_ymd_opt(2017, 4, 1).unwrap().and_hms_opt(12, 0, 0).unwrap(),
NaiveDate::from_ymd_opt(2017, 4, 1).unwrap().and_hms_opt(15, 0, 0).unwrap(),
]
);
assert_eq!(
surface_section
.into_iter()
.map(|sd| sd.mslp)
.collect::<Vec<_>>(),
vec![
some(HectoPascal(1020.4)),
some(HectoPascal(1021.5)),
some(HectoPascal(1022.3)),
some(HectoPascal(1022.7)),
some(HectoPascal(1022.6)),
some(HectoPascal(1021.8))
]
);
assert_eq!(
surface_section
.into_iter()
.map(|sd| sd.station_pres)
.collect::<Vec<_>>(),
vec![
some(HectoPascal(909.1)),
some(HectoPascal(909.4)),
some(HectoPascal(909.4)),
some(HectoPascal(909.3)),
some(HectoPascal(908.8)),
some(HectoPascal(908.6))
]
);
assert_eq!(
surface_section
.into_iter()
.map(|sd| sd.low_cloud)
.collect::<Vec<_>>(),
vec![
some(1.0),
some(0.52),
some(0.02),
some(0.01),
some(0.03),
some(0.04)
]
);
assert_eq!(
surface_section
.into_iter()
.map(|sd| sd.mid_cloud)
.collect::<Vec<_>>(),
vec![some(0.0); 6]
);
assert_eq!(
surface_section
.into_iter()
.map(|sd| sd.hi_cloud)
.collect::<Vec<_>>(),
vec![
some(0.58),
some(0.61),
some(0.39),
some(0.33),
some(0.49),
some(0.77)
]
);
assert_eq!(
surface_section
.into_iter()
.map(|sd| sd.wind)
.collect::<Vec<_>>(),
vec![
some(WindSpdDir::<Knots>::from(WindUV {
u: MetersPSec(0.9),
v: MetersPSec(-0.1)
})),
some(WindSpdDir::<Knots>::from(WindUV {
u: MetersPSec(1.3),
v: MetersPSec(0.5)
})),
some(WindSpdDir::<Knots>::from(WindUV {
u: MetersPSec(1.1),
v: MetersPSec(0.6)
})),
some(WindSpdDir::<Knots>::from(WindUV {
u: MetersPSec(1.1),
v: MetersPSec(0.6)
})),
some(WindSpdDir::<Knots>::from(WindUV {
u: MetersPSec(0.6),
v: MetersPSec(0.8)
})),
some(WindSpdDir::<Knots>::from(WindUV {
u: MetersPSec(0.4),
v: MetersPSec(0.6)
}))
]
);
}
#[test]
fn test_validate() {
let surface_section = SurfaceSection::init(get_valid_test_data()).unwrap();
assert!(surface_section.validate_section().is_ok());
println!("DOING TEST 1");
let surface_section = SurfaceSection::init(get_invalid_test_data1()).unwrap();
assert!(!surface_section.validate_section().is_ok());
println!("DONE TEST 1");
assert!(SurfaceSection::init(get_invalid_test_data2()).is_err());
}
}