epw_rs/
epw_file.rs

1/*!
2This module contains the definition for the [EPWFile] struct that the parsing API is built around.
3
4It implements two important methods, [EPWFile::from_path] and [EPWFile::from_reader], which handle
5parsing the specified file, or provided file content.
6
7*/
8use crate::error::EPWParseError;
9use crate::header::parse_header;
10use crate::weather_data::PresentWeather;
11use crate::{Header, WeatherData};
12use chrono::LocalResult::Single;
13use chrono::{FixedOffset, TimeZone};
14use std::fs::File;
15use std::io::{BufRead, BufReader, Lines};
16
17/// EPWFile is the representation of the parsed file
18///
19/// it has only two attributes, `header` which is an instance of the [Header] struct,
20/// and `data` which contains the weather data in a [WeatherData] struct.
21#[derive(Debug)]
22pub struct EPWFile {
23    pub header: Header,
24    pub data: WeatherData,
25}
26
27impl EPWFile {
28    /// Construct an EPWFile instance from a buffered reader.
29    /// ## Type Parameters
30    ///  - `R`: the type of the reader
31    ///
32    /// ## Parameters
33    /// - `reader`:  Reader that returns file contents.
34    ///
35    /// ## Returns
36    /// An initialized EPWReader or an EPWParseError
37    ///
38    pub fn from_reader<R: BufRead>(reader: R) -> Result<Self, EPWParseError> {
39        let mut lines = reader.lines();
40        let header = parse_header(&mut lines)?;
41        let data = _parse_data(&mut lines, &header)?;
42
43        Ok(Self { header, data })
44    }
45
46    /// Create an EPWFile instance from a file path
47    ///
48    /// ## Parameters
49    /// - `path`: Path to file on the filesystem
50    ///
51    /// ## Returns
52    /// An initialized EPWFile or an EPWParseError
53    pub fn from_path(path: &str) -> Result<Self, EPWParseError> {
54        let f = match File::open(path) {
55            Ok(val) => val,
56            Err(e) => return Err(EPWParseError::FileNotFound(e.to_string())),
57        };
58
59        let reader = BufReader::new(f);
60        Self::from_reader(reader)
61    }
62}
63
64fn _parse_data<R: BufRead>(
65    lines: &mut Lines<R>,
66    header: &Header,
67) -> Result<WeatherData, EPWParseError> {
68    // TODO: Don't panic
69    let estimated_capacity = 8760 * header.data_periods.records_per_hour;
70    let mut data = WeatherData {
71        timestamp: Vec::with_capacity(estimated_capacity),
72        flags: Vec::with_capacity(estimated_capacity),
73        dry_bulb_temperature: Vec::with_capacity(estimated_capacity),
74        dew_point_temperature: Vec::with_capacity(estimated_capacity),
75        relative_humidity: Vec::with_capacity(estimated_capacity),
76        atmospheric_pressure: Vec::with_capacity(estimated_capacity),
77        extraterrestrial_horizontal_radiation: Vec::with_capacity(estimated_capacity),
78        extraterrestrial_direct_normal_radiation: Vec::with_capacity(estimated_capacity),
79        horizontal_infrared_radiation_intensity: Vec::with_capacity(estimated_capacity),
80        global_horizontal_radiation: Vec::with_capacity(estimated_capacity),
81        direct_normal_radiation: Vec::with_capacity(estimated_capacity),
82        diffuse_horizontal_radiation: Vec::with_capacity(estimated_capacity),
83        global_horizontal_illuminance: Vec::with_capacity(estimated_capacity),
84        direct_normal_illuminance: Vec::with_capacity(estimated_capacity),
85        diffuse_horizontal_illuminance: Vec::with_capacity(estimated_capacity),
86        zenith_luminance: Vec::with_capacity(estimated_capacity),
87        wind_direction: Vec::with_capacity(estimated_capacity),
88        wind_speed: Vec::with_capacity(estimated_capacity),
89        total_sky_cover: Vec::with_capacity(estimated_capacity),
90        opaque_sky_cover: Vec::with_capacity(estimated_capacity),
91        visibility: Vec::with_capacity(estimated_capacity),
92        ceiling_height: Vec::with_capacity(estimated_capacity),
93        present_weather_observation: Vec::with_capacity(estimated_capacity),
94        present_weather_codes: Vec::with_capacity(estimated_capacity),
95        precipitable_water: Vec::with_capacity(estimated_capacity),
96        aerosol_optical_depth: Vec::with_capacity(estimated_capacity),
97        snow_depth: Vec::with_capacity(estimated_capacity),
98        days_since_last_snowfall: Vec::with_capacity(estimated_capacity),
99        albedo: Vec::with_capacity(estimated_capacity),
100        liquid_precipitation_depth: Vec::with_capacity(estimated_capacity),
101        liquid_precipitation_quantity: Vec::with_capacity(estimated_capacity),
102    };
103
104    for line in lines {
105        let line = line.expect("Unable to read line");
106        _parse_row(&line, &mut data, &header.location.time_zone)?
107    }
108
109    Ok(data)
110}
111
112fn _parse_row(
113    line: &str,
114    dest: &mut WeatherData,
115    timezone: &FixedOffset,
116) -> Result<(), EPWParseError> {
117    let parts = line.split(",").collect::<Vec<&str>>();
118    if parts.len() < 32 {
119        return Err(EPWParseError::Data(format!("Invalid Data Row: {}", line)));
120    }
121
122    let year = match parts[0].parse() {
123        Ok(val) => val,
124        Err(e) => {
125            return Err(EPWParseError::Data(format!(
126                "Invalid Year: {} [{}]",
127                parts[0], e
128            )))
129        }
130    };
131    let month = match parts[1].parse() {
132        Ok(val) => val,
133        Err(e) => {
134            return Err(EPWParseError::Data(format!(
135                "Invalid Month: {} [{}]",
136                parts[1], e
137            )))
138        }
139    };
140    let day = match parts[2].parse() {
141        Ok(val) => val,
142        Err(e) => {
143            return Err(EPWParseError::Data(format!(
144                "Invalid Day: {} [{}]",
145                parts[2], e
146            )))
147        }
148    };
149
150    let hour: u32 = match parts[3].parse() {
151        Ok(val) => val,
152        Err(e) => {
153            return Err(EPWParseError::Data(format!(
154                "Invalid Hour: {} [{}]",
155                parts[3], e
156            )))
157        }
158    };
159    let minute = match parts[4].parse() {
160        Ok(val) => val,
161        Err(e) => {
162            return Err(EPWParseError::Data(format!(
163                "Invalid Minute: {} [{}]",
164                parts[4], e
165            )))
166        }
167    };
168
169    let timestamp = match timezone.with_ymd_and_hms(
170        year,
171        month,
172        day,
173        hour - 1,
174        match minute == 60 {
175            true => 0,
176            false => minute,
177        },
178        0,
179    ) {
180        Single(val) => val,
181        _ => {
182            return Err(EPWParseError::Data(format!(
183                "Invalid Timestamp: {}-{}-{} {}:{}:00",
184                year, month, day, hour, minute
185            )))
186        }
187    };
188
189    let dry_bulb_temperature = _parse_float_value(parts[6], "Dry Bulb Temperature", 99.9)?;
190    let dew_point_temperature = _parse_float_value(parts[7], "Dew Point Temperature", 99.9)?;
191    let relative_humidity = _parse_float_value(parts[8], "Relative Humidity", 999.)?;
192    let atmospheric_pressure = _parse_float_value(parts[9], "Atmospheric Pressure", 999999.)?;
193    let extraterrestrial_horizontal_radiation =
194        _parse_float_value(parts[10], "Extraterrestrial Horizontal Radiation", 9999.)?;
195    let extraterrestrial_direct_normal_radiation =
196        _parse_float_value(parts[11], "Extraterrestrial Direct Normal Radiation", 9999.)?;
197    let horizontal_infrared_radiation_intensity =
198        _parse_float_value(parts[12], "Horizontal Infrared Radiation Intensity", 9999.)?;
199    let global_horizontal_radiation =
200        _parse_float_value(parts[13], "Global Horizontal Radiation", 9999.)?;
201    let direct_normal_radiation = _parse_float_value(parts[14], "Direct Normal Radiation", 9999.)?;
202    let diffuse_horizontal_radiation =
203        _parse_float_value(parts[15], "Diffuse Horizontal Radiation", 9999.)?;
204
205    let global_horizontal_illuminance = match parts[16].parse() {
206        Ok(val) => match val < 999900. {
207            true => val,
208            false => f64::NAN,
209        },
210        Err(e) => {
211            return Err(EPWParseError::Data(format!(
212                "Invalid Global Horizontal Illuminance: {}",
213                e
214            )))
215        }
216    };
217
218    let direct_normal_illuminance = match parts[17].parse() {
219        Ok(val) => match val < 999900. {
220            true => val,
221            false => f64::NAN,
222        },
223        Err(e) => {
224            return Err(EPWParseError::Data(format!(
225                "Invalid Direct Normal Illuminance: {}",
226                e
227            )))
228        }
229    };
230
231    let diffuse_horizontal_illuminance = match parts[18].parse() {
232        Ok(val) => match val < 999900. {
233            true => val,
234            false => f64::NAN,
235        },
236        Err(e) => {
237            return Err(EPWParseError::Data(format!(
238                "Invalid Diffuse Horizontal Illuminance: {}",
239                e
240            )))
241        }
242    };
243
244    let zenith_luminance = _parse_float_value(parts[19], "Zenith Luminance", 9999.)?;
245    let wind_direction = _parse_float_value(parts[20], "Wind Direction", 999.)?;
246    let wind_speed = _parse_float_value(parts[21], "Wind Speed", 999.)?;
247    let total_sky_cover = _parse_float_value(parts[22], "Total Sky Cover", 99.)?;
248    let opaque_sky_cover = _parse_float_value(parts[23], "Opaque Sky Cover", 99.)?;
249    let visibility = _parse_float_value(parts[24], "Visibility", 9999.)?;
250    let ceiling_height = _parse_float_value(parts[25], "Ceiling Height", 99999.)?;
251
252    let present_weather = _parse_present_weather(parts[27])?;
253
254    let precipitable_water = _parse_float_value(parts[28], "Precipitable water", 999.)?;
255    let aerosol_optical_depth = _parse_float_value(parts[29], "Aerosol Optical Depth", 999.)?;
256    let snow_depth = _parse_float_value(parts[30], "Snow Depth", 999.)?;
257    let days_since_last_snowfall = _parse_float_value(parts[31], "Days Since Last Snowfall", 99.)?;
258
259    let albedo = match parts.len() > 32 {
260        true => _parse_float_value(parts[32], "Albedo", 999.)?,
261        false => f64::NAN,
262    };
263
264    let liquid_precipitation_depth = match parts.len() > 33 {
265        true => parts[33].parse().unwrap(),
266        false => f64::NAN,
267    };
268
269    let liquid_precipitation_quantity = match parts.len() > 34 {
270        true => parts[34].parse().unwrap(),
271        false => f64::NAN,
272    };
273
274    dest.timestamp.push(timestamp);
275    dest.flags.push(parts[5].to_string());
276    dest.dry_bulb_temperature.push(dry_bulb_temperature);
277    dest.dew_point_temperature.push(dew_point_temperature);
278    dest.relative_humidity.push(relative_humidity);
279    dest.atmospheric_pressure.push(atmospheric_pressure);
280    dest.extraterrestrial_horizontal_radiation
281        .push(extraterrestrial_horizontal_radiation);
282    dest.extraterrestrial_direct_normal_radiation
283        .push(extraterrestrial_direct_normal_radiation);
284    dest.horizontal_infrared_radiation_intensity
285        .push(horizontal_infrared_radiation_intensity);
286    dest.global_horizontal_radiation
287        .push(global_horizontal_radiation);
288    dest.direct_normal_radiation.push(direct_normal_radiation);
289    dest.diffuse_horizontal_radiation
290        .push(diffuse_horizontal_radiation);
291    dest.global_horizontal_illuminance
292        .push(global_horizontal_illuminance);
293    dest.direct_normal_illuminance
294        .push(direct_normal_illuminance);
295    dest.diffuse_horizontal_illuminance
296        .push(diffuse_horizontal_illuminance);
297    dest.zenith_luminance.push(zenith_luminance);
298    dest.wind_direction.push(wind_direction);
299    dest.wind_speed.push(wind_speed);
300    dest.total_sky_cover.push(total_sky_cover);
301    dest.opaque_sky_cover.push(opaque_sky_cover);
302    dest.visibility.push(visibility);
303    dest.ceiling_height.push(ceiling_height);
304    dest.present_weather_observation.push(parts[26] == "0");
305
306    dest.present_weather_codes.push(present_weather);
307    dest.precipitable_water.push(precipitable_water);
308    dest.aerosol_optical_depth.push(aerosol_optical_depth);
309    dest.snow_depth.push(snow_depth);
310    dest.days_since_last_snowfall.push(days_since_last_snowfall);
311    dest.albedo.push(albedo);
312    dest.liquid_precipitation_depth
313        .push(liquid_precipitation_depth);
314    dest.liquid_precipitation_quantity
315        .push(liquid_precipitation_quantity);
316    Ok(())
317}
318
319fn _parse_present_weather(condition_str: &str) -> Result<PresentWeather, EPWParseError> {
320    let thunderstorm = match condition_str[0..1].parse() {
321        Ok(val) => val,
322        Err(e) => {
323            return Err(EPWParseError::Data(format!(
324                "Invalid Conditions: {} [{}]",
325                &condition_str, e
326            )))
327        }
328    };
329
330    let rain = match condition_str[1..2].parse() {
331        Ok(val) => val,
332        Err(e) => {
333            return Err(EPWParseError::Data(format!(
334                "Invalid Conditions: {} [{}]",
335                &condition_str, e
336            )))
337        }
338    };
339
340    let rain_squalls = match condition_str[2..3].parse() {
341        Ok(val) => val,
342        Err(e) => {
343            return Err(EPWParseError::Data(format!(
344                "Invalid Conditions: {} [{}]",
345                &condition_str, e
346            )))
347        }
348    };
349
350    let snow = match condition_str[3..4].parse() {
351        Ok(val) => val,
352        Err(e) => {
353            return Err(EPWParseError::Data(format!(
354                "Invalid Conditions: {} [{}]",
355                &condition_str, e
356            )))
357        }
358    };
359
360    let snow_showers = match condition_str[4..5].parse() {
361        Ok(val) => val,
362        Err(e) => {
363            return Err(EPWParseError::Data(format!(
364                "Invalid Conditions: {} [{}]",
365                &condition_str, e
366            )))
367        }
368    };
369
370    let sleet = match condition_str[5..6].parse() {
371        Ok(val) => val,
372        Err(e) => {
373            return Err(EPWParseError::Data(format!(
374                "Invalid Conditions: {} [{}]",
375                &condition_str, e
376            )))
377        }
378    };
379
380    let fog = match condition_str[6..7].parse() {
381        Ok(val) => val,
382        Err(e) => {
383            return Err(EPWParseError::Data(format!(
384                "Invalid Conditions: {} [{}]",
385                &condition_str, e
386            )))
387        }
388    };
389
390    let smoke = match condition_str[7..8].parse() {
391        Ok(val) => val,
392        Err(e) => {
393            return Err(EPWParseError::Data(format!(
394                "Invalid Conditions: {} [{}]",
395                &condition_str, e
396            )))
397        }
398    };
399
400    let ice_pellets = match condition_str[8..9].parse() {
401        Ok(val) => val,
402        Err(e) => {
403            return Err(EPWParseError::Data(format!(
404                "Invalid Conditions: {} [{}]",
405                &condition_str, e
406            )))
407        }
408    };
409
410    Ok(PresentWeather {
411        thunderstorm,
412        rain,
413        rain_squalls,
414        snow,
415        snow_showers,
416        sleet,
417        fog,
418        smoke,
419        ice_pellets,
420    })
421}
422
423fn _parse_float_value(value: &str, name: &str, missing_value: f64) -> Result<f64, EPWParseError> {
424    let value = match value.parse() {
425        Ok(val) => match val != missing_value {
426            true => val,
427            false => f64::NAN,
428        },
429        Err(e) => {
430            return Err(EPWParseError::Data(format!(
431                "Invalid {} value: {}",
432                name, e
433            )))
434        }
435    };
436    Ok(value)
437}