1use 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#[derive(Debug)]
22pub struct EPWFile {
23 pub header: Header,
24 pub data: WeatherData,
25}
26
27impl EPWFile {
28 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 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 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}