1use std::{ops::{Div, Mul}, str::FromStr};
9
10use anyhow::{anyhow, Error, Result};
11use chrono::{NaiveDateTime, NaiveTime, Datelike, Duration};
12use chronoutil::RelativeDuration;
13use lazy_static::lazy_static;
14use regex::Regex;
15use serde::{Serialize, Deserialize};
16
17use crate::datetime::{UtcDateTime, UtcDayTime, UtcTime};
18
19lazy_static! {
20 static ref WHITESPACE_REPLACE_RE: Regex = Regex::new(r"\s+").unwrap();
21 static ref WHITESPACE_REPLACE_OUT: &'static str = " ";
22
23 static ref END_REPLACE_RE: Regex = Regex::new(r"[\s=]*$").unwrap();
24 static ref END_REPLACE_OUT: &'static str = " ";
25
26 static ref SECTION_RE: Regex = Regex::new(r"(?x)
27 ^(?P<section>NOSIG|TEMPO|BECMG|RMK)
28 (?P<end>\s)
29 ").unwrap();
30
31 static ref HEADER_RE: Regex = Regex::new(r"(?x)
32 ^(?P<station_id>[A-Z][A-Z0-9]{3})
33 \s
34 (?P<day>\d\d)
35 (?P<hour>\d\d)
36 (?P<minute>\d\d)\d?Z?
37 (\s(?P<corrected>COR|CC[A-Z]))?
38 (\s(?P<auto>AUTO))?
39 (?P<end>\s)
40 ").unwrap();
41
42 static ref WIND_RE: Regex = Regex::new(r"(?x)
43 ^E?(?P<direction>\d\d\d|VRB|///)
44 (?P<speed>P?\d\d|//)
45 (G(?P<gust>P?\d\d|//))?
46 (?P<units>KT|MPS)
47 (\s(?P<direction_range>\d\d\dV\d\d\d))?
48 (?P<end>\s)
49 ").unwrap();
50
51 static ref VISIBILITY_RE: Regex = Regex::new(r"(?x)
52 ^(?P<prevailing>[MP]?(\d+\s)?\d/\d{1,2}|[MP]?\d{1,5}|////|[CK]AVOK)
53 (NDV)?
54 \s?
55 (?P<units>SM|KM)?
56 (\s(?P<minimum>[MP]?\d{1,4}))?
57 (?P<directional>(\s[MP]?\d{1,4}[NESW][EW]?)+)?
58 (?P<end>\s)
59 ").unwrap();
60
61 static ref DIRECTIONAL_VISIBILITY_RE: Regex = Regex::new(r"(?x)
62 ^(?P<visibility>[MP]?\d{1,4})
63 (?P<direction>[NESW][EW]?)
64 ").unwrap();
65
66 static ref RUNWAY_VISUAL_RANGE_RE: Regex = Regex::new(r"(?x)
67 ^R(?P<runway>\d\d[A-Z]?)
68 /
69 (?P<visual_range>[MP]?\d\d\d\d(V[MP]?\d\d\d\d)?)
70 (?P<units>FT)?
71 /?
72 (?P<trend>[UDN])?
73 (?P<end>\s)
74 ").unwrap();
75
76 static ref PRESENT_WEATHER_RE: Regex = Regex::new(r"(?x)
77 ^(?P<intensity>[-\+])?
78 (?P<code>(VC|MI|BC|PR|DR|BL|SH|TS|FZ|DZ|RA|SN|SG|PL|GR|GS|UP|BR|FG|FU|VA|DU|SA|HZ|PO|SQ|FC|SS|DS|IC|PY|NSW)+)
79 (?P<end>\s)
80 ").unwrap();
81
82 static ref CLOUD_RE: Regex = Regex::new(r"(?x)
83 ^(?P<cover>CLR|SKC|NSC|NCD|FEW|SCT|BKN|OVC|VV|///)
84 (?P<height>\d{1,3}|///)?
85 (?P<cloud>AC|ACC|ACSL|AS|CB|CBMAM|CC|CCSL|CI|CS|CU|NS|SC|SCSL|ST|TC?U|///)?
86 (?P<end>\s)
87 ").unwrap();
88
89 static ref TEMPERATURE_RE: Regex = Regex::new(r"(?x)
90 ^(?P<temperature>M?\d{1,2}|//|XX)
91 /
92 (?P<dew_point>M?\d{1,2}|//|XX)?
93 (?P<end>\s)
94 ").unwrap();
95
96 static ref PRESSURE_RE: Regex = Regex::new(r"(?x)
97 ^(?P<units>A|Q)
98 (?P<pressure>\d{3,4}|////)
99 (?P<end>\s)
100 ").unwrap();
101
102 static ref RECENT_WEATHER_RE: Regex = Regex::new(r"(?x)
103 ^RE(?P<intensity>[-\+])?
104 (?P<code>(VC|MI|BC|PR|DR|BL|SH|TS|FZ|DZ|RA|SN|SG|PL|GR|GS|UP|BR|FG|FU|VA|DU|SA|HZ|PO|SQ|FC|SS|DS|IC|PY|NSW)+)
105 (?P<end>\s)
106 ").unwrap();
107
108 static ref WIND_SHEAR_RE: Regex = Regex::new(r"(?x)
109 ^WS
110 \s
111 (?P<runway>R\d\d[A-Z]?|ALL\sRWY)
112 (?P<end>\s)
113 ").unwrap();
114
115 static ref SEA_RE: Regex = Regex::new(r"(?x)
116 ^W(?P<temperature>M?\d{1,2}|//|XX)
117 /
118 (S(?P<state>\d|/))?
119 (H(?P<height>\d{1,3}|///))?
120 (?P<end>\s)
121 ").unwrap();
122
123 static ref COLOR_RE: Regex = Regex::new(r"(?x)
124 ^(BLACK|BLU\+?|GRN|WHT|RED|AMB|YLO)+
125 (?P<end>\s)
126 ").unwrap();
127
128 static ref RAINFALL_RE: Regex = Regex::new(r"(?x)
129 ^RF[\d/]{2}[\./][\d/]/[\d/]{3}[\./][\d/]
130 (?P<end>\s)
131 ").unwrap();
132
133 static ref RUNWAY_STATE_RE: Regex = Regex::new(r"(?x)
134 ^R\d\d[A-Z]?/([\d/]{6}|CLRD[\d/]{2})
135 (?P<end>\s)
136 ").unwrap();
137
138 static ref TREND_TIME_RE: Regex = Regex::new(r"(?x)
139 ^(?P<indicator>FM|TL|AT)
140 \s?
141 (?P<hour>\d\d)
142 (?P<minute>\d\d)Z?
143 (?P<end>\s)
144 ").unwrap();
145}
146
147#[non_exhaustive]
151#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
152#[serde(rename_all = "snake_case")]
153pub enum Trend {
154 #[default]
156 NoSignificantChange,
157 Temporary,
159 Becoming,
161}
162
163impl FromStr for Trend {
164 type Err = Error;
165
166 fn from_str(s: &str) -> Result<Self, Self::Err> {
167 match s {
168 "NOSIG" => Ok(Trend::NoSignificantChange),
169 "TEMPO" => Ok(Trend::Temporary),
170 "BECMG" => Ok(Trend::Becoming),
171 _ => Err(anyhow!("Invalid trend, given {}", s))
172 }
173 }
174}
175
176#[non_exhaustive]
177#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
178#[serde(rename_all = "snake_case")]
179enum Section {
180 Main,
181 Trend(Trend),
182 Remark,
183}
184
185impl FromStr for Section {
186 type Err = Error;
187
188 fn from_str(s: &str) -> Result<Self, Self::Err> {
189 match s {
190 "RMK" => Ok(Section::Remark),
191 s => match Trend::from_str(s) {
192 Ok(trend) => Ok(Section::Trend(trend)),
193 Err(_) => Err(anyhow!("Invalid section, given {}", s))
194 }
195 }
196 }
197}
198
199fn handle_section(text: &str) -> Option<(Section, usize)> {
200 SECTION_RE.captures(text)
201 .map(|capture| {
202 let section = Section::from_str(&capture["section"]).unwrap();
203 let end = capture.name("end").unwrap().end();
204
205 (section, end)
206 })
207}
208
209#[non_exhaustive]
219#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
220#[serde(tag = "value_type", content = "value", rename_all = "snake_case")]
221pub enum MetarTime {
222 DateTime(UtcDateTime),
224 DayTime(UtcDayTime),
226 Time(UtcTime),
228}
229
230impl MetarTime {
231 pub fn to_date_time(&self, anchor_time: NaiveDateTime) -> MetarTime {
237 match self {
238 MetarTime::DateTime(utc_dt) => MetarTime::DateTime(*utc_dt),
239 MetarTime::DayTime(utc_d_t) => {
240 let first_guess_opt = anchor_time.date().with_day(utc_d_t.0).map(|nd| nd.and_time(utc_d_t.1));
241 let second_guess_opt = (anchor_time + RelativeDuration::months(-1)).date().with_day(utc_d_t.0).map(|nd| nd.and_time(utc_d_t.1));
242 let third_guess_opt = (anchor_time + RelativeDuration::months(1)).date().with_day(utc_d_t.0).map(|nd| nd.and_time(utc_d_t.1));
243
244 let mut final_guess_opt = None;
245 let mut final_delta = i64::MAX;
246
247 for guess_opt in [first_guess_opt, second_guess_opt, third_guess_opt] {
248 if let Some(guess) = guess_opt {
249 let delta = guess.signed_duration_since(anchor_time).num_seconds().abs();
250 if delta < final_delta {
251 final_guess_opt = guess_opt;
252 final_delta = delta;
253 }
254 }
255 }
256
257 match final_guess_opt {
258 Some(final_guess) => MetarTime::DateTime(UtcDateTime(final_guess)),
259 None => panic!("{}", format!("Date guessing failed, given time {:?} and anchor time {}", self, anchor_time))
260 }
261 },
262 MetarTime::Time(utc_t) => {
263 let first_guess = anchor_time.date().and_time(utc_t.0);
264 let second_guess = first_guess + Duration::days(-1);
265 let third_guess = first_guess + Duration::days(1);
266
267 let mut final_guess = first_guess;
268 let mut final_delta = final_guess.signed_duration_since(anchor_time).num_seconds().abs();
269
270 for guess in [second_guess, third_guess] {
271 let delta = guess.signed_duration_since(anchor_time).num_seconds().abs();
272 if delta < final_delta {
273 final_guess = guess;
274 final_delta = delta;
275 }
276 }
277
278 MetarTime::DateTime(UtcDateTime(final_guess))
279 },
280 }
281 }
282}
283
284#[non_exhaustive]
286#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
287pub struct Header {
288 pub station_id: Option<String>,
290 pub observation_time: Option<MetarTime>,
292 pub is_corrected: Option<bool>,
294 pub is_automated: Option<bool>,
296}
297
298impl Header {
299 fn is_empty(&self) -> bool {
300 self.station_id.is_none() && self.observation_time.is_none() && self.is_corrected.is_none() && self.is_automated.is_none()
301 }
302}
303
304fn handle_header(text: &str, anchor_time: Option<NaiveDateTime>) -> Option<(Header, usize)> {
305 HEADER_RE.captures(text)
306 .map(|capture| {
307 let station_id = Some(capture["station_id"].to_string());
308
309 let day = capture["day"].parse().unwrap();
310 let hour = capture["hour"].parse().unwrap();
311 let minute = capture["minute"].parse().unwrap();
312
313 let naive_time = NaiveTime::from_hms_opt(hour, minute, 0);
314 let mut time = naive_time.map(|nt| MetarTime::DayTime(UtcDayTime(day, nt)));
315
316 if let Some(at) = anchor_time {
317 time = time.map(|t| t.to_date_time(at));
318 }
319
320 let is_corrected = Some(capture.name("corrected").is_some());
321
322 let is_automated = Some(capture.name("auto").is_some());
323
324 let end = capture.name("end").unwrap().end();
325
326 let header = Header { station_id, observation_time: time, is_corrected, is_automated };
327
328 (header, end)
329 })
330}
331
332#[non_exhaustive]
336#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
337pub enum Unit {
338 #[serde(rename = "degT")]
345 DegreeTrue,
346 #[serde(rename = "kt")]
353 Knot,
354 #[serde(rename = "m/s")]
361 MetrePerSecond,
362 #[serde(rename = "km")]
369 KiloMetre,
370 #[serde(rename = "m")]
377 Metre,
378 #[serde(rename = "mi")]
385 StatuteMile,
386 #[serde(rename = "ft")]
393 Foot,
394 #[serde(rename = "degC")]
401 DegreeCelsius,
402 #[serde(rename = "hPa")]
409 HectoPascal,
410 #[serde(rename = "inHg")]
417 InchOfMercury,
418}
419
420impl FromStr for Unit {
421 type Err = Error;
422
423 fn from_str(s: &str) -> Result<Self, Self::Err> {
424 match s {
425 "KT" => Ok(Unit::Knot),
426 "MPS" => Ok(Unit::MetrePerSecond),
427 "KM" => Ok(Unit::KiloMetre),
428 "SM" => Ok(Unit::StatuteMile),
429 "FT" => Ok(Unit::Foot),
430 "Q" => Ok(Unit::HectoPascal),
431 "A" => Ok(Unit::InchOfMercury),
432 _ => Err(anyhow!("Invalid units, given {}", s))
433 }
434 }
435}
436
437fn parse_value(s: &str) -> Result<f32> {
438 if s.contains(' ') && s.contains('/') {
439 let mut split_space = s.split(' ');
440 let number: f32 = split_space.next().unwrap().parse()?;
441
442 let mut split_slash = split_space.next().unwrap().split('/');
443 let numerator: f32 = split_slash.next().unwrap().parse()?;
444 let denominator: f32 = split_slash.next().unwrap().parse()?;
445
446 Ok(number + numerator / denominator)
447 } else if s.contains('/') {
448 let mut split = s.split('/');
449 let numerator: f32 = split.next().unwrap().parse()?;
450 let denominator: f32 = split.next().unwrap().parse()?;
451
452 Ok(numerator / denominator)
453 } else {
454 Ok(s.parse()?)
455 }
456}
457
458#[non_exhaustive]
468#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
469#[serde(tag = "value_type", content = "value", rename_all = "snake_case")]
470pub enum ValueInRange {
471 Above(f32),
473 Below(f32),
475 Exact(f32),
477}
478
479impl FromStr for ValueInRange {
480 type Err = Error;
481
482 fn from_str(s: &str) -> Result<Self, Self::Err> {
483 if let Some(stripped) = s.strip_prefix('P') {
484 let value = parse_value(stripped).unwrap();
485 Ok(ValueInRange::Above(value))
486 } else if let Some(stripped) = s.strip_prefix('M') {
487 let value = parse_value(stripped).unwrap();
488 Ok(ValueInRange::Below(value))
489 } else {
490 let value = parse_value(s).unwrap();
491 Ok(ValueInRange::Exact(value))
492 }
493 }
494}
495
496impl Div<f32> for ValueInRange {
497 type Output = ValueInRange;
498
499 fn div(self, rhs: f32) -> Self::Output {
500 match self {
501 ValueInRange::Above(x) => ValueInRange::Above(x / rhs),
502 ValueInRange::Below(x) => ValueInRange::Below(x / rhs),
503 ValueInRange::Exact(x) => ValueInRange::Exact(x / rhs),
504 }
505 }
506}
507
508impl Mul<f32> for ValueInRange {
509 type Output = ValueInRange;
510
511 fn mul(self, rhs: f32) -> Self::Output {
512 match self {
513 ValueInRange::Above(x) => ValueInRange::Above(x * rhs),
514 ValueInRange::Below(x) => ValueInRange::Below(x * rhs),
515 ValueInRange::Exact(x) => ValueInRange::Exact(x * rhs),
516 }
517 }
518}
519
520#[non_exhaustive]
530#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
531#[serde(tag = "value_type", content = "value", rename_all = "snake_case")]
532pub enum Value {
533 Variable,
535 Above(f32),
537 Below(f32),
539 Range(ValueInRange, ValueInRange),
541 Exact(f32),
543}
544
545impl FromStr for Value {
546 type Err = Error;
547
548 fn from_str(s: &str) -> Result<Self, Self::Err> {
549 if s == "VRB" {
550 Ok(Value::Variable)
551 } else if s.contains('V') {
552 let mut split = s.split('V');
553 let value1 = ValueInRange::from_str(split.next().unwrap()).unwrap();
554 let value2 = ValueInRange::from_str(split.next().unwrap()).unwrap();
555 Ok(Value::Range(value1, value2))
556 } else if let Some(stripped) = s.strip_prefix('P') {
557 let value = parse_value(stripped).unwrap();
558 Ok(Value::Above(value))
559 } else if let Some(stripped) = s.strip_prefix('M') {
560 let value = parse_value(stripped).unwrap();
561 Ok(Value::Below(value))
562 } else {
563 let value = parse_value(s).unwrap();
564 Ok(Value::Exact(value))
565 }
566 }
567}
568
569impl Div<f32> for Value {
570 type Output = Value;
571
572 fn div(self, rhs: f32) -> Self::Output {
573 match self {
574 Value::Variable => Value::Variable,
575 Value::Above(x) => Value::Above(x / rhs),
576 Value::Below(x) => Value::Below(x / rhs),
577 Value::Range(x, y) => Value::Range(x / rhs, y / rhs),
578 Value::Exact(x) => Value::Exact(x / rhs),
579 }
580 }
581}
582
583impl Mul<f32> for Value {
584 type Output = Value;
585
586 fn mul(self, rhs: f32) -> Self::Output {
587 match self {
588 Value::Variable => Value::Variable,
589 Value::Above(x) => Value::Above(x * rhs),
590 Value::Below(x) => Value::Below(x * rhs),
591 Value::Range(x, y) => Value::Range(x * rhs, y * rhs),
592 Value::Exact(x) => Value::Exact(x * rhs),
593 }
594 }
595}
596
597#[non_exhaustive]
599#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
600pub struct Quantity {
601 #[serde(flatten)]
605 pub value: Value,
606 pub units: Unit,
607}
608
609impl Quantity {
610 fn new(value: Value, units: Unit) -> Quantity {
611 Quantity { value, units }
612 }
613
614 fn new_opt(value: Option<Value>, units: Unit) -> Option<Quantity> {
615 value.map(|v| Quantity { value: v, units })
616 }
617}
618
619#[non_exhaustive]
621#[derive(Debug, Clone, Copy, PartialEq, Default, Serialize, Deserialize)]
622pub struct Wind {
623 pub wind_from_direction: Option<Quantity>,
625 pub wind_from_direction_range: Option<Quantity>,
628 pub wind_speed: Option<Quantity>,
629 pub wind_gust: Option<Quantity>,
630}
631
632impl Wind {
633 fn is_empty(&self) -> bool {
634 self.wind_from_direction.is_none() && self.wind_from_direction_range.is_none() && self.wind_speed.is_none() && self.wind_gust.is_none()
635 }
636}
637
638fn handle_wind(text: &str) -> Option<(Wind, usize)> {
639 WIND_RE.captures(text)
640 .map(|capture| {
641 let mut from_direction_value = match &capture["direction"] {
642 "///" => None,
643 s => Some(Value::from_str(s).unwrap()),
644 };
645
646 if &capture["direction"] == "000" && &capture["speed"] == "00" {
647 from_direction_value = None;
649 }
650
651 let speed_value = match &capture["speed"] {
652 "//" => None,
653 s => Some(Value::from_str(s).unwrap()),
654 };
655
656 let gust_value = capture.name("gust").and_then(|c| match c.as_str() {
657 "//" => None,
658 s => Some(Value::from_str(s).unwrap()),
659 });
660
661 let units = Unit::from_str(&capture["units"]).unwrap();
662
663 let from_direction_range_value = capture.name("direction_range")
664 .map(|s| Value::from_str(s.as_str()).unwrap());
665
666 let wind_from_direction = Quantity::new_opt(from_direction_value, Unit::DegreeTrue);
667 let wind_from_direction_range = Quantity::new_opt(from_direction_range_value, Unit::DegreeTrue);
668 let wind_speed = Quantity::new_opt(speed_value, units);
669 let wind_gust = Quantity::new_opt(gust_value, units);
670
671 let end = capture.name("end").unwrap().end();
672
673 let wind = Wind { wind_from_direction, wind_from_direction_range, wind_speed, wind_gust };
674
675 (wind, end)
676 })
677}
678
679#[non_exhaustive]
683#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
684#[serde(rename_all = "snake_case")]
685pub enum DirectionOctant {
686 North,
687 NorthEast,
688 East,
689 SouthEast,
690 South,
691 SouthWest,
692 West,
693 NorthWest,
694}
695
696impl FromStr for DirectionOctant {
697 type Err = Error;
698
699 fn from_str(s: &str) -> Result<Self, Self::Err> {
700 match s {
701 "N" => Ok(DirectionOctant::North),
702 "NE" => Ok(DirectionOctant::NorthEast),
703 "E" => Ok(DirectionOctant::East),
704 "SE" => Ok(DirectionOctant::SouthEast),
705 "S" => Ok(DirectionOctant::South),
706 "SW" => Ok(DirectionOctant::SouthWest),
707 "W" => Ok(DirectionOctant::West),
708 "NW" => Ok(DirectionOctant::NorthWest),
709 _ => Err(anyhow!("Invalid direction octant, given {}", s))
710 }
711 }
712}
713
714#[non_exhaustive]
716#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
717pub struct DirectionalVisibility {
718 pub visibility: Quantity,
719 pub direction: DirectionOctant,
720}
721
722#[non_exhaustive]
724#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
725pub struct Visibility {
726 pub prevailing_visibility: Option<Quantity>,
727 pub minimum_visibility: Option<Quantity>,
728 pub directional_visibilites: Vec<DirectionalVisibility>,
729}
730
731impl Visibility {
732 fn is_empty(&self) -> bool {
733 self.prevailing_visibility.is_none() && self.minimum_visibility.is_none() && self.directional_visibilites.is_empty()
734 }
735}
736
737fn handle_visibility(text: &str) -> Option<(Visibility, bool, usize)> {
738 VISIBILITY_RE.captures(text)
739 .map(|capture| {
740 let mut is_cavok = false;
741
742 let mut prevailing_visibility_value = match &capture["prevailing"] {
743 "////" => None,
744 "CAVOK" | "KAVOK" => {
745 is_cavok = true;
746 Some(Value::Above(10000.0))
747 },
748 s => Some(Value::from_str(s).unwrap()),
749 };
750
751 let units = capture.name("units")
752 .map(|c| Unit::from_str(c.as_str()).unwrap())
753 .unwrap_or(Unit::Metre);
754
755 if prevailing_visibility_value == Some(Value::Exact(9999.0)) && units == Unit::Metre {
756 prevailing_visibility_value = Some(Value::Above(10000.0));
757 }
758
759 let minimum_visibility_value = capture.name("minimum").map(|c| Value::from_str(c.as_str()).unwrap());
760
761 let directional_visibilites = capture.name("directional")
762 .map(|c| c.as_str().split(' ')
763 .map(|group| DIRECTIONAL_VISIBILITY_RE.captures(group))
764 .filter(|capture| capture.is_some())
765 .map(|capture| DirectionalVisibility {
766 visibility: Quantity::new(Value::from_str(&capture.as_ref().unwrap()["visibility"]).unwrap(), units),
767 direction: DirectionOctant::from_str(&capture.unwrap()["direction"]).unwrap(),
768 })
769 .collect::<Vec<_>>())
770 .unwrap_or_default();
771
772 let prevailing_visibility = Quantity::new_opt(prevailing_visibility_value, units);
773 let minimum_visibility = Quantity::new_opt(minimum_visibility_value, units);
774
775 let end = capture.name("end").unwrap().end();
776
777 let visibility = Visibility { prevailing_visibility, minimum_visibility, directional_visibilites };
778
779 (visibility, is_cavok, end)
780 })
781}
782
783#[non_exhaustive]
787#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
788#[serde(rename_all = "snake_case")]
789pub enum RunwayVisualRangeTrend {
790 Increasing,
791 Decreasing,
792 NoChange,
793}
794
795impl FromStr for RunwayVisualRangeTrend {
796 type Err = Error;
797
798 fn from_str(s: &str) -> Result<Self, Self::Err> {
799 match s {
800 "U" => Ok(RunwayVisualRangeTrend::Increasing),
801 "D" => Ok(RunwayVisualRangeTrend::Decreasing),
802 "N" => Ok(RunwayVisualRangeTrend::NoChange),
803 _ => Err(anyhow!("Invalid runway visual range trend, given {}", s))
804 }
805 }
806}
807
808#[non_exhaustive]
810#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
811pub struct RunwayVisualRange {
812 pub runway: String,
813 pub visual_range: Quantity,
814 pub trend: Option<RunwayVisualRangeTrend>,
815}
816
817fn handle_runway_visual_range(text: &str) -> Option<(RunwayVisualRange, usize)> {
818 RUNWAY_VISUAL_RANGE_RE.captures(text)
819 .map(|capture| {
820 let runway = capture["runway"].to_string();
821
822 let visual_range_value = Value::from_str(&capture["visual_range"]).unwrap();
823
824 let units = capture.name("units")
825 .map(|c| Unit::from_str(c.as_str()).unwrap())
826 .unwrap_or(Unit::Metre);
827
828 let trend = capture.name("trend")
829 .map(|c| RunwayVisualRangeTrend::from_str(c.as_str()).unwrap());
830
831 let visual_range = Quantity::new(visual_range_value, units);
832
833 let end = capture.name("end").unwrap().end();
834
835 let rvr = RunwayVisualRange { runway, visual_range, trend };
836
837 (rvr, end)
838 })
839}
840
841#[non_exhaustive]
845#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
846#[serde(rename_all = "snake_case")]
847pub enum WeatherIntensity {
848 Light,
849 Moderate,
850 Heavy,
851}
852
853impl FromStr for WeatherIntensity {
854 type Err = Error;
855
856 fn from_str(s: &str) -> Result<Self, Self::Err> {
857 match s {
858 "-" => Ok(WeatherIntensity::Light),
859 "+" => Ok(WeatherIntensity::Heavy),
860 _ => Err(anyhow!("Invalid weather intensity, given {}", s))
861 }
862 }
863}
864
865#[non_exhaustive]
869#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
870#[serde(rename_all = "snake_case")]
871pub enum WeatherDescriptor {
872 Shallow,
873 Patches,
874 Partial,
875 LowDrifting,
876 Blowing,
877 Shower,
878 Thunderstorm,
879 Freezing,
880}
881
882impl FromStr for WeatherDescriptor {
883 type Err = Error;
884
885 fn from_str(s: &str) -> Result<Self, Self::Err> {
886 match s {
887 "MI" => Ok(WeatherDescriptor::Shallow),
888 "BC" => Ok(WeatherDescriptor::Patches),
889 "PR" => Ok(WeatherDescriptor::Partial),
890 "DR" => Ok(WeatherDescriptor::LowDrifting),
891 "BL" => Ok(WeatherDescriptor::Blowing),
892 "SH" => Ok(WeatherDescriptor::Shower),
893 "TS" => Ok(WeatherDescriptor::Thunderstorm),
894 "FZ" => Ok(WeatherDescriptor::Freezing),
895 _ => Err(anyhow!("Invalid weather descriptor, given {}", s))
896 }
897 }
898}
899
900#[non_exhaustive]
904#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
905#[serde(rename_all = "snake_case")]
906pub enum WeatherPhenomena {
907 Drizzle,
908 Rain,
909 Snow,
910 SnowGrains,
911 IcePellets,
912 Hail,
913 SnowPellets,
914 UnknownPrecipitation,
915 Mist,
916 Fog,
917 Smoke,
918 VolcanicAsh,
919 Dust,
920 Sand,
921 Haze,
922 DustWhirls,
923 Squalls,
924 FunnelCloud,
925 Sandstorm,
926 Duststorm,
927 IceCrystals,
928 Spray,
929 NilSignificantWeather,
930}
931
932impl FromStr for WeatherPhenomena {
933 type Err = Error;
934
935 fn from_str(s: &str) -> Result<Self, Self::Err> {
936 match s {
937 "DZ" => Ok(WeatherPhenomena::Drizzle),
938 "RA" => Ok(WeatherPhenomena::Rain),
939 "SN" => Ok(WeatherPhenomena::Snow),
940 "SG" => Ok(WeatherPhenomena::SnowGrains),
941 "PL" => Ok(WeatherPhenomena::IcePellets),
942 "GR" => Ok(WeatherPhenomena::Hail),
943 "GS" => Ok(WeatherPhenomena::SnowPellets),
944 "UP" => Ok(WeatherPhenomena::UnknownPrecipitation),
945 "BR" => Ok(WeatherPhenomena::Mist),
946 "FG" => Ok(WeatherPhenomena::Fog),
947 "FU" => Ok(WeatherPhenomena::Smoke),
948 "VA" => Ok(WeatherPhenomena::VolcanicAsh),
949 "DU" => Ok(WeatherPhenomena::Dust),
950 "SA" => Ok(WeatherPhenomena::Sand),
951 "HZ" => Ok(WeatherPhenomena::Haze),
952 "PO" => Ok(WeatherPhenomena::DustWhirls),
953 "SQ" => Ok(WeatherPhenomena::Squalls),
954 "FC" => Ok(WeatherPhenomena::FunnelCloud),
955 "SS" => Ok(WeatherPhenomena::Sandstorm),
956 "DS" => Ok(WeatherPhenomena::Duststorm),
957 "IC" => Ok(WeatherPhenomena::IceCrystals),
958 "PY" => Ok(WeatherPhenomena::Spray),
959 "NSW" => Ok(WeatherPhenomena::NilSignificantWeather),
960 _ => Err(anyhow!("Invalid weather phenomena, given {}", s))
961 }
962 }
963}
964
965#[non_exhaustive]
967#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
968pub struct WeatherCondition {
969 pub intensity: WeatherIntensity,
970 pub is_in_vicinity: bool,
973 pub descriptors: Vec<WeatherDescriptor>,
974 pub phenomena: Vec<WeatherPhenomena>,
975}
976
977fn handle_weather(weather_re: &Regex, text: &str) -> Option<(WeatherCondition, usize)> {
978 weather_re.captures(text)
979 .map(|capture| {
980 let intensity = capture.name("intensity")
981 .map(|c| WeatherIntensity::from_str(c.as_str()).unwrap())
982 .unwrap_or(WeatherIntensity::Moderate);
983
984 let groups = if &capture["code"] == "NSW" {
985 vec!["NSW".to_string()]
986 } else {
987 capture["code"].chars()
988 .collect::<Vec<_>>()
989 .chunks(2)
990 .map(String::from_iter)
991 .collect::<Vec<_>>()
992 };
993
994 let mut is_in_vicinity = false;
995 let mut descriptors = Vec::new();
996 let mut phenomena = Vec::new();
997
998 for group in groups.iter() {
999 if group == "VC" {
1000 is_in_vicinity = true;
1001 } else if let Ok(wd) = WeatherDescriptor::from_str(group) {
1002 descriptors.push(wd);
1003 } else if let Ok(wp) = WeatherPhenomena::from_str(group) {
1004 phenomena.push(wp);
1005 }
1006 }
1007
1008 let end = capture.name("end").unwrap().end();
1009
1010 let weather = WeatherCondition { intensity, is_in_vicinity, descriptors, phenomena };
1011
1012 (weather, end)
1013 })
1014}
1015
1016fn handle_present_weather(text: &str) -> Option<(WeatherCondition, usize)> {
1017 handle_weather(&PRESENT_WEATHER_RE, text)
1018}
1019
1020fn handle_recent_weather(text: &str) -> Option<(WeatherCondition, usize)> {
1021 handle_weather(&RECENT_WEATHER_RE, text)
1022}
1023
1024#[non_exhaustive]
1028#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1029#[serde(rename_all = "snake_case")]
1030pub enum CloudCover {
1031 Clear,
1032 SkyClear,
1033 NilSignificantCloud,
1034 NoCloudDetected,
1035 Few,
1036 Scattered,
1037 Broken,
1038 Overcast,
1039 VerticalVisibility,
1041 CeilingOk,
1043}
1044
1045impl FromStr for CloudCover {
1046 type Err = Error;
1047
1048 fn from_str(s: &str) -> Result<Self, Self::Err> {
1049 match s {
1050 "CLR" => Ok(CloudCover::Clear),
1051 "SKC" => Ok(CloudCover::SkyClear),
1052 "NSC" => Ok(CloudCover::NilSignificantCloud),
1053 "NCD" => Ok(CloudCover::NoCloudDetected),
1054 "FEW" => Ok(CloudCover::Few),
1055 "SCT" => Ok(CloudCover::Scattered),
1056 "BKN" => Ok(CloudCover::Broken),
1057 "OVC" => Ok(CloudCover::Overcast),
1058 "VV" => Ok(CloudCover::VerticalVisibility),
1059 _ => Err(anyhow!("Invalid cloud cover, given {}", s))
1060 }
1061 }
1062}
1063
1064#[non_exhaustive]
1068#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1069#[serde(rename_all = "snake_case")]
1070pub enum CloudType {
1071 Altocumulus,
1072 AltocumulusCastellanus,
1073 AltocumulusLenticularis,
1074 Altostratus,
1075 Cumulonimbus,
1076 CumulonimbusMammatus,
1077 Cirrocumulus,
1078 CirrocumulusLenticularis,
1079 Cirrus,
1080 Cirrostratus,
1081 Cumulus,
1082 Nimbostratus,
1083 Stratocumulus,
1084 StratocumulusLenticularis,
1085 Stratus,
1086 ToweringCumulus,
1087}
1088
1089impl FromStr for CloudType {
1090 type Err = Error;
1091
1092 fn from_str(s: &str) -> Result<Self, Self::Err> {
1093 match s {
1094 "AC" => Ok(CloudType::Altocumulus),
1095 "ACC" => Ok(CloudType::AltocumulusCastellanus),
1096 "ACSL" => Ok(CloudType::AltocumulusLenticularis),
1097 "AS" => Ok(CloudType::Altostratus),
1098 "CB" => Ok(CloudType::Cumulonimbus),
1099 "CBMAM" => Ok(CloudType::CumulonimbusMammatus),
1100 "CC" => Ok(CloudType::Cirrocumulus),
1101 "CCSL" => Ok(CloudType::CirrocumulusLenticularis),
1102 "CI" => Ok(CloudType::Cirrus),
1103 "CS" => Ok(CloudType::Cirrostratus),
1104 "CU" => Ok(CloudType::Cumulus),
1105 "NS" => Ok(CloudType::Nimbostratus),
1106 "SC" => Ok(CloudType::Stratocumulus),
1107 "SCSL" => Ok(CloudType::StratocumulusLenticularis),
1108 "ST" => Ok(CloudType::Stratus),
1109 "TCU" | "TU" => Ok(CloudType::ToweringCumulus),
1110 _ => Err(anyhow!("Invalid cloud type, given {s}"))
1111 }
1112 }
1113}
1114
1115#[non_exhaustive]
1117#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
1118pub struct CloudLayer {
1119 pub cover: Option<CloudCover>,
1120 pub height: Option<Quantity>,
1122 pub cloud_type: Option<CloudType>,
1123}
1124
1125impl CloudLayer {
1126 fn is_empty(&self) -> bool {
1127 self.cover.is_none() && self.height.is_none() && self.cloud_type.is_none()
1128 }
1129}
1130
1131fn handle_cloud_layer(text: &str) -> Option<(CloudLayer, usize)> {
1132 CLOUD_RE.captures(text)
1133 .map(|capture| {
1134 let cover = match &capture["cover"] {
1135 "///" => None,
1136 s => Some(CloudCover::from_str(s).unwrap()),
1137 };
1138
1139 let height_value = capture.name("height").and_then(|c| match c.as_str() {
1140 "///" => None,
1141 s => Some(Value::from_str(s).unwrap() * 100.0),
1142 });
1143
1144 let cloud_type = capture.name("cloud").and_then(|c| match c.as_str() {
1145 "///" => None,
1146 s => Some(CloudType::from_str(s).unwrap()),
1147 });
1148
1149 let height = Quantity::new_opt(height_value, Unit::Foot);
1150
1151 let end = capture.name("end").unwrap().end();
1152
1153 let cloud_layer = CloudLayer { cover, height, cloud_type };
1154
1155 (cloud_layer, end)
1156 })
1157}
1158
1159#[non_exhaustive]
1161#[derive(Debug, Clone, Copy, PartialEq, Default, Serialize, Deserialize)]
1162pub struct Temperature {
1163 pub temperature: Option<Quantity>,
1164 pub dew_point: Option<Quantity>,
1165}
1166
1167impl Temperature {
1168 fn is_empty(&self) -> bool {
1169 self.temperature.is_none() && self.dew_point.is_none()
1170 }
1171}
1172
1173fn handle_temperature(text: &str) -> Option<(Temperature, usize)> {
1174 TEMPERATURE_RE.captures(text)
1175 .map(|capture| {
1176 let temperature_value = match &capture["temperature"] {
1177 "//" | "XX" => None,
1178 s => Some(Value::from_str(&s.replace('M', "-")).unwrap()),
1179 };
1180
1181 let dew_point_value = capture.name("dew_point").and_then(|c| match c.as_str() {
1182 "//" | "XX" => None,
1183 s => Some(Value::from_str(&s.replace('M', "-")).unwrap()),
1184 });
1185
1186 let temperature = Quantity::new_opt(temperature_value, Unit::DegreeCelsius);
1187 let dew_point = Quantity::new_opt(dew_point_value, Unit::DegreeCelsius);
1188
1189 let end = capture.name("end").unwrap().end();
1190
1191 let temperature = Temperature { temperature, dew_point };
1192
1193 (temperature, end)
1194 })
1195}
1196
1197#[non_exhaustive]
1199#[derive(Debug, Clone, Copy, PartialEq, Default, Serialize, Deserialize)]
1200pub struct Pressure {
1201 pub pressure: Option<Quantity>,
1202}
1203
1204impl Pressure {
1205 fn is_empty(&self) -> bool {
1206 self.pressure.is_none()
1207 }
1208}
1209
1210fn handle_pressure(text: &str) -> Option<(Pressure, usize)> {
1211 PRESSURE_RE.captures(text)
1212 .map(|capture| {
1213 let mut pressure_value = match &capture["pressure"] {
1214 "////" => None,
1215 s => Some(Value::from_str(s).unwrap()),
1216 };
1217
1218 let units = Unit::from_str(&capture["units"]).unwrap();
1219
1220 if units == Unit::InchOfMercury {
1221 pressure_value = pressure_value.map(|p| p / 100.0)
1222 }
1223
1224 let pressure = Quantity::new_opt(pressure_value, units);
1225
1226 let end = capture.name("end").unwrap().end();
1227
1228 let pressure = Pressure { pressure };
1229
1230 (pressure, end)
1231 })
1232}
1233
1234#[non_exhaustive]
1236#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1237pub struct WindShear {
1238 pub runway: String,
1239}
1240
1241fn handle_wind_shear(text: &str) -> Option<(WindShear, usize)> {
1242 WIND_SHEAR_RE.captures(text)
1243 .map(|capture| {
1244 let runway = match &capture["runway"] {
1245 "ALL RWY" => "all".to_string(),
1246 s => s[1..].to_string(),
1247 };
1248
1249 let end = capture.name("end").unwrap().end();
1250
1251 let ws = WindShear { runway };
1252
1253 (ws, end)
1254 })
1255}
1256
1257#[non_exhaustive]
1261#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1262#[serde(rename_all = "snake_case")]
1263pub enum SeaState {
1264 Glassy,
1265 Rippled,
1266 Smooth,
1267 Slight,
1268 Moderate,
1269 Rough,
1270 VeryRough,
1271 High,
1272 VeryHigh,
1273 Phenomenal,
1274}
1275
1276impl FromStr for SeaState {
1277 type Err = Error;
1278
1279 fn from_str(s: &str) -> Result<Self, Self::Err> {
1280 match s {
1281 "0" => Ok(SeaState::Glassy),
1282 "1" => Ok(SeaState::Rippled),
1283 "2" => Ok(SeaState::Smooth),
1284 "3" => Ok(SeaState::Slight),
1285 "4" => Ok(SeaState::Moderate),
1286 "5" => Ok(SeaState::Rough),
1287 "6" => Ok(SeaState::VeryRough),
1288 "7" => Ok(SeaState::High),
1289 "8" => Ok(SeaState::VeryHigh),
1290 "9" => Ok(SeaState::Phenomenal),
1291 _ => Err(anyhow!("Invalid sea state, given {}", s))
1292 }
1293 }
1294}
1295
1296#[non_exhaustive]
1298#[derive(Debug, Clone, Copy, PartialEq, Default, Serialize, Deserialize)]
1299pub struct Sea {
1300 pub sea_temperature: Option<Quantity>,
1301 pub sea_state: Option<SeaState>,
1302 pub wave_height: Option<Quantity>,
1303}
1304
1305impl Sea {
1306 fn is_empty(&self) -> bool {
1307 self.sea_temperature.is_none() && self.sea_state.is_none() && self.wave_height.is_none()
1308 }
1309}
1310
1311fn handle_sea(text: &str) -> Option<(Sea, usize)> {
1312 SEA_RE.captures(text)
1313 .map(|capture| {
1314 let temperature_value = match &capture["temperature"] {
1315 "//" | "XX" => None,
1316 s => Some(Value::from_str(&s.replace('M', "-")).unwrap()),
1317 };
1318
1319 let sea_state = capture.name("state").and_then(|c| match c.as_str() {
1320 "/" => None,
1321 s => Some(SeaState::from_str(s).unwrap()),
1322 });
1323
1324 let height_value = capture.name("height").and_then(|c| match c.as_str() {
1325 "///" => None,
1326 s => Some(Value::from_str(s).unwrap() / 10.0),
1327 });
1328
1329 let sea_temperature = Quantity::new_opt(temperature_value, Unit::DegreeCelsius);
1330 let wave_height = Quantity::new_opt(height_value, Unit::Metre);
1331
1332 let end = capture.name("end").unwrap().end();
1333
1334 let sea = Sea { sea_temperature, sea_state, wave_height };
1335
1336 (sea, end)
1337 })
1338}
1339
1340fn handle_color(text: &str) -> Option<usize> {
1341 COLOR_RE.captures(text)
1342 .map(|capture| {
1343 capture.name("end").unwrap().end()
1344 })
1345}
1346
1347fn handle_rainfall(text: &str) -> Option<usize> {
1348 RAINFALL_RE.captures(text)
1349 .map(|capture| {
1350 capture.name("end").unwrap().end()
1351 })
1352}
1353
1354fn handle_runway_state(text: &str) -> Option<usize> {
1355 RUNWAY_STATE_RE.captures(text)
1356 .map(|capture| {
1357 capture.name("end").unwrap().end()
1358 })
1359}
1360
1361#[non_exhaustive]
1362#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1363#[serde(rename_all = "snake_case")]
1364enum TrendTimeIndicator {
1365 From,
1366 Until,
1367 At,
1368}
1369
1370impl FromStr for TrendTimeIndicator {
1371 type Err = Error;
1372
1373 fn from_str(s: &str) -> Result<Self, Self::Err> {
1374 match s {
1375 "FM" => Ok(TrendTimeIndicator::From),
1376 "TL" => Ok(TrendTimeIndicator::Until),
1377 "AT" => Ok(TrendTimeIndicator::At),
1378 _ => Err(anyhow!("Invalid trend time indicator, given {}", s))
1379 }
1380 }
1381}
1382
1383#[non_exhaustive]
1384#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1385struct TrendTime {
1386 indicator: TrendTimeIndicator,
1387 time: Option<MetarTime>,
1388}
1389
1390fn handle_trend_time(text: &str, anchor_time: Option<NaiveDateTime>) -> Option<(TrendTime, usize)> {
1391 TREND_TIME_RE.captures(text)
1392 .map(|capture| {
1393 let indicator = TrendTimeIndicator::from_str(&capture["indicator"]).unwrap();
1394 let mut hour = capture["hour"].parse().unwrap();
1395 let minute = capture["minute"].parse().unwrap();
1396
1397 if hour == 24 {
1398 hour = 0;
1399 }
1400
1401 let naive_time = NaiveTime::from_hms_opt(hour, minute, 0);
1402 let mut time = naive_time.map(|nt| MetarTime::Time(UtcTime(nt)));
1403
1404 if let Some(at) = anchor_time {
1405 time = time.map(|t| t.to_date_time(at));
1406 }
1407
1408 let end = capture.name("end").unwrap().end();
1409
1410 let trend_time = TrendTime { indicator, time };
1411
1412 (trend_time, end)
1413 })
1414}
1415
1416#[non_exhaustive]
1420#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
1421pub struct TrendChange {
1422 pub indicator: Trend,
1423 pub from_time: Option<MetarTime>,
1424 pub to_time: Option<MetarTime>,
1425 pub at_time: Option<MetarTime>,
1426 #[serde(flatten)]
1430 pub wind: Wind,
1431 #[serde(flatten)]
1435 pub visibility: Visibility,
1436 pub weather: Vec<WeatherCondition>,
1437 pub clouds: Vec<CloudLayer>,
1438}
1439
1440#[non_exhaustive]
1442#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
1443pub struct Metar {
1444 #[serde(flatten)]
1448 pub header: Header,
1449 #[serde(flatten)]
1453 pub wind: Wind,
1454 #[serde(flatten)]
1458 pub visibility: Visibility,
1459 pub runway_visual_ranges: Vec<RunwayVisualRange>,
1460 pub present_weather: Vec<WeatherCondition>,
1461 pub clouds: Vec<CloudLayer>,
1462 #[serde(flatten)]
1466 pub temperature: Temperature,
1467 #[serde(flatten)]
1471 pub pressure: Pressure,
1472 pub recent_weather: Vec<WeatherCondition>,
1473 pub wind_shears: Vec<WindShear>,
1474 #[serde(flatten)]
1478 pub sea: Sea,
1479 pub trend_changes: Vec<TrendChange>,
1480 pub report: String,
1481}
1482
1483pub fn decode_metar(report: &str, anchor_time: Option<NaiveDateTime>) -> Result<Metar> {
1491 let mut sanitized = report.to_uppercase().trim().replace('\x00', "");
1492 sanitized = WHITESPACE_REPLACE_RE.replace_all(&sanitized, *WHITESPACE_REPLACE_OUT).to_string();
1493 let report = END_REPLACE_RE.replace_all(&sanitized, *END_REPLACE_OUT).to_string();
1494
1495 let mut section = Section::Main;
1496
1497 let mut metar = Metar::default();
1498 metar.report = report.trim().to_string();
1499
1500 let mut processing_trend_change = false;
1501 let mut trend_change = TrendChange::default();
1502
1503 let mut unparsed_groups = Vec::new();
1504
1505 let mut idx = 0;
1514
1515 while idx < report.len() {
1516 let sub_report = &report[idx..];
1517
1518 if let Some((sec, relative_end)) = handle_section(sub_report) {
1519 section = sec;
1520 idx += relative_end;
1521
1522 if processing_trend_change {
1523 metar.trend_changes.push(trend_change.clone());
1524 processing_trend_change = false;
1525 trend_change = TrendChange::default();
1526 }
1527
1528 if let Section::Trend(trend) = section {
1529 processing_trend_change = true;
1530 trend_change.indicator = trend;
1531 }
1532
1533 continue;
1534 }
1535
1536 match section {
1537 Section::Main => {
1538 if metar.header.is_empty() {
1539 if let Some((header, relative_end)) = handle_header(sub_report, anchor_time) {
1540 metar.header = header;
1541 idx += relative_end;
1542 continue;
1543 }
1544 }
1545
1546 if metar.wind.is_empty() {
1547 if let Some((wind, relative_end)) = handle_wind(sub_report) {
1548 metar.wind = wind;
1549 idx += relative_end;
1550 continue;
1551 }
1552 }
1553
1554 if metar.visibility.is_empty() {
1555 if let Some((visibility, is_cavok, relative_end)) = handle_visibility(sub_report) {
1556 metar.visibility = visibility;
1557
1558 if is_cavok {
1559 let cloud_layer = CloudLayer { cover: Some(CloudCover::CeilingOk) , height: None, cloud_type: None };
1560 metar.clouds.push(cloud_layer);
1561 }
1562
1563 idx += relative_end;
1564 continue;
1565 }
1566 }
1567
1568 if let Some((weather_condition, relative_end)) = handle_present_weather(sub_report) {
1569 metar.present_weather.push(weather_condition);
1570 idx += relative_end;
1571 continue;
1572 }
1573
1574 if let Some((runway_visual_range, relative_end)) = handle_runway_visual_range(sub_report) {
1575 metar.runway_visual_ranges.push(runway_visual_range);
1576 idx += relative_end;
1577 continue;
1578 }
1579
1580 if let Some((cloud_layer, relative_end)) = handle_cloud_layer(sub_report) {
1581 if !cloud_layer.is_empty() {
1582 metar.clouds.push(cloud_layer);
1583 }
1584
1585 idx += relative_end;
1586 continue;
1587 }
1588
1589 if metar.temperature.is_empty() {
1590 if let Some((temperature, relative_end)) = handle_temperature(sub_report) {
1591 if !temperature.is_empty() {
1592 metar.temperature = temperature;
1593 }
1594
1595 idx += relative_end;
1596 continue;
1597 }
1598 }
1599
1600 if metar.pressure.is_empty() {
1601 if let Some((pressure, relative_end)) = handle_pressure(sub_report) {
1602 metar.pressure = pressure;
1603 idx += relative_end;
1604 continue;
1605 }
1606 }
1607
1608 if let Some((weather_condition, relative_end)) = handle_recent_weather(sub_report) {
1609 metar.recent_weather.push(weather_condition);
1610 idx += relative_end;
1611 continue;
1612 }
1613
1614 if let Some((wind_shear, relative_end)) = handle_wind_shear(sub_report) {
1615 metar.wind_shears.push(wind_shear);
1616 idx += relative_end;
1617 continue;
1618 }
1619
1620 if metar.sea.is_empty() {
1621 if let Some((sea, relative_end)) = handle_sea(sub_report) {
1622 if !sea.is_empty() {
1623 metar.sea = sea;
1624 }
1625
1626 idx += relative_end;
1627 continue;
1628 }
1629 }
1630
1631 if let Some(relative_end) = handle_color(sub_report) {
1634 idx += relative_end;
1635 continue;
1636 }
1637
1638 if let Some(relative_end) = handle_rainfall(sub_report) {
1641 idx += relative_end;
1642 continue;
1643 }
1644
1645 if let Some(relative_end) = handle_runway_state(sub_report) {
1648 idx += relative_end;
1649 continue;
1650 }
1651 },
1652 Section::Trend(_) => {
1653 if let Some((trend_time, relative_end)) = handle_trend_time(sub_report, anchor_time) {
1654 match trend_time.indicator {
1655 TrendTimeIndicator::From => {
1656 trend_change.from_time = trend_time.time;
1657 },
1658 TrendTimeIndicator::Until => {
1659 trend_change.to_time = trend_time.time;
1660 },
1661 TrendTimeIndicator::At => {
1662 trend_change.at_time = trend_time.time;
1663 },
1664 }
1665
1666 idx += relative_end;
1667 continue;
1668 }
1669
1670 if trend_change.wind.is_empty() {
1671 if let Some((wind, relative_end)) = handle_wind(sub_report) {
1672 trend_change.wind = wind;
1673 idx += relative_end;
1674 continue;
1675 }
1676 }
1677
1678 if trend_change.visibility.is_empty() {
1679 if let Some((visibility, is_cavok, relative_end)) = handle_visibility(sub_report) {
1680 trend_change.visibility = visibility;
1681
1682 if is_cavok {
1683 let cloud_layer = CloudLayer { cover: Some(CloudCover::CeilingOk) , height: None, cloud_type: None };
1684 trend_change.clouds.push(cloud_layer);
1685 }
1686
1687 idx += relative_end;
1688 continue;
1689 }
1690 }
1691
1692 if let Some((weather_condition, relative_end)) = handle_present_weather(sub_report) {
1693 trend_change.weather.push(weather_condition);
1694 idx += relative_end;
1695 continue;
1696 }
1697
1698 if let Some((cloud_layer, relative_end)) = handle_cloud_layer(sub_report) {
1699 if !cloud_layer.is_empty() {
1700 trend_change.clouds.push(cloud_layer);
1701 }
1702
1703 idx += relative_end;
1704 continue;
1705 }
1706 },
1707 Section::Remark => (), }
1709
1710 let relative_end = sub_report.find(' ').unwrap();
1711
1712 let unparsed = &report[idx..idx + relative_end];
1713 if unparsed.chars().any(|c| c != '/') {
1714 unparsed_groups.push(unparsed);
1715 }
1716
1717 idx += relative_end + 1;
1718 }
1719
1720 if processing_trend_change {
1721 metar.trend_changes.push(trend_change);
1722 }
1723
1724 if !unparsed_groups.is_empty() {
1725 log::debug!("Unparsed data: {}, report: {}", unparsed_groups.join(" "), report);
1726 }
1727
1728 Ok(metar)
1729}