sounding_bufkit/
bufkit_data.rs

1//! Module for reading a bufkit file and breaking it into smaller pieces for parsing later.
2use std::collections::HashMap;
3use std::error::Error;
4use std::path::Path;
5
6mod combine;
7mod surface;
8mod surface_section;
9mod upper_air;
10mod upper_air_section;
11
12use sounding_analysis::Sounding;
13
14use self::surface_section::{SurfaceIterator, SurfaceSection};
15use self::upper_air_section::{UpperAirIterator, UpperAirSection};
16use crate::error::*;
17
18/// Hold an entire bufkit file in memory.
19pub struct BufkitFile {
20    file_text: String,
21    file_name: String,
22}
23
24impl BufkitFile {
25    /// Load a file into memory.
26    pub fn load(path: &Path) -> Result<BufkitFile, Box<dyn Error>> {
27        use std::fs::File;
28        use std::io::prelude::Read;
29        use std::io::BufReader;
30
31        // Load the file contents
32        let mut file = BufReader::new(File::open(path)?);
33        let mut contents = String::new();
34        file.read_to_string(&mut contents)?;
35
36        Ok(BufkitFile {
37            file_text: contents,
38            file_name: path
39                .file_name()
40                .map(|s| s.to_string_lossy().to_string())
41                .unwrap_or_else(|| "Unknown File".to_owned()),
42        })
43    }
44
45    /// Validate the whole file, ensure it is parseable and do some sanity checks.
46    pub fn validate_file_format(&self) -> Result<(), Box<dyn Error>> {
47        let data = self.data()?;
48        data.validate()?;
49
50        Ok(())
51    }
52
53    /// Get a bufkit data object from this file.
54    pub fn data(&self) -> Result<BufkitData<'_>, Box<dyn Error>> {
55        BufkitData::init(&self.file_text, &self.file_name)
56    }
57
58    /// Get the raw string data from the file.
59    pub fn raw_text(&self) -> &str {
60        &self.file_text
61    }
62}
63
64/// References to different data sections within a `BufkitFile` mainly useful for generating
65/// iterators.
66///
67/// This is theoretically not necessary without lexical lifetimes.
68pub struct BufkitData<'a> {
69    upper_air: UpperAirSection<'a>,
70    surface: SurfaceSection<'a>,
71    file_name: &'a str,
72}
73
74impl<'a> BufkitData<'a> {
75    /// Validate the whole string, ensure it is parseable and do some sanity checks.
76    pub fn validate(&self) -> Result<(), Box<dyn Error>> {
77        self.upper_air.validate_section()?;
78        self.surface.validate_section()?;
79        Ok(())
80    }
81
82    /// Initialize struct for parsing a sounding.
83    pub fn init(text: &'a str, fname: &'a str) -> Result<BufkitData<'a>, Box<dyn Error>> {
84        let break_point = BufkitData::find_break_point(text)?;
85        let data = BufkitData::new_with_break_point(text, break_point, fname)?;
86        Ok(data)
87    }
88
89    fn new_with_break_point(
90        text: &'a str,
91        break_point: usize,
92        fname: &'a str,
93    ) -> Result<BufkitData<'a>, BufkitFileError> {
94        Ok(BufkitData {
95            upper_air: UpperAirSection::new(&text[0..break_point]),
96            surface: SurfaceSection::init(&text[break_point..])?,
97            file_name: fname,
98        })
99    }
100
101    fn find_break_point(text: &str) -> Result<usize, BufkitFileError> {
102        match text.find("STN YYMMDD/HHMM") {
103            None => Err(BufkitFileError::new()),
104            Some(val) => Ok(val),
105        }
106    }
107}
108
109impl<'a> IntoIterator for &'a BufkitData<'a> {
110    type Item = (Sounding, HashMap<&'static str, f64>);
111    type IntoIter = SoundingIterator<'a>;
112
113    fn into_iter(self) -> Self::IntoIter {
114        SoundingIterator {
115            upper_air_it: self.upper_air.into_iter(),
116            surface_it: self.surface.into_iter(),
117            source_name: self.file_name,
118        }
119    }
120}
121
122/// Iterator type for `BufkitData` that returns a `Sounding`.
123pub struct SoundingIterator<'a> {
124    upper_air_it: UpperAirIterator<'a>,
125    surface_it: SurfaceIterator<'a>,
126    source_name: &'a str,
127}
128
129impl<'a> Iterator for SoundingIterator<'a> {
130    type Item = (Sounding, HashMap<&'static str, f64>);
131
132    fn next(&mut self) -> Option<Self::Item> {
133        let mut next_ua = self.upper_air_it.next()?;
134        let mut next_sd = self.surface_it.next()?;
135
136        loop {
137            while next_sd.valid_time < next_ua.valid_time {
138                next_sd = self.surface_it.next()?;
139            }
140            while next_ua.valid_time < next_sd.valid_time {
141                next_ua = self.upper_air_it.next()?;
142            }
143            if next_ua.valid_time == next_sd.valid_time {
144                return Some(combine::combine_data(next_ua, next_sd, &self.source_name));
145            }
146        }
147    }
148}