sounding_validate/
validate.rs1use crate::error::*;
2use metfor::{Celsius, HectoPascal, Meters, WindSpdDir};
3use sounding_base::Sounding;
4
5use optional::{some, Optioned};
6
7macro_rules! validate_f64_positive {
8 ($var:expr, $var_name:expr, $err_list:ident) => {
9 if let Some(val) = $var.into_option() {
10 let val: f64 = metfor::Quantity::unpack(val);
11 if val < 0.0 {
12 $err_list.push_error(Err(ValidationError::InvalidNegativeValue($var_name, val)));
13 }
14 }
15 };
16}
17
18macro_rules! validate_wind_direction {
19 ($var:expr, $err_list:ident) => {
20 if let Some(val) = $var.into() {
21 if val < 0.0 || val > 360.0 {
22 $err_list.push_error(Err(ValidationError::InvalidWindDirection(val)));
23 }
24 }
25 };
26}
27
28pub fn validate(snd: &Sounding) -> Result<(), ValidationErrors> {
31 let mut err_return = ValidationErrors::new();
32
33 let pressure = snd.pressure_profile();
34
35 err_return.push_error(check_pressure_exists(pressure));
41
42 let len = pressure.len();
43 let temperature = snd.temperature_profile();
44 let wet_bulb = snd.wet_bulb_profile();
45 let dew_point = snd.dew_point_profile();
46 let theta_e = snd.theta_e_profile();
47 let wind = snd.wind_profile();
48 let omega = snd.pvv_profile();
49 let height = snd.height_profile();
50 let cloud_fraction = snd.cloud_fraction_profile();
51
52 err_return.push_error(validate_vector_len(temperature, len, "Temperature"));
53 err_return.push_error(validate_vector_len(wet_bulb, len, "Wet bulb temperature"));
54 err_return.push_error(validate_vector_len(dew_point, len, "Dew point"));
55 err_return.push_error(validate_vector_len(theta_e, len, "Theta-e"));
56 err_return.push_error(validate_vector_len(wind, len, "Wind"));
57 err_return.push_error(validate_vector_len(
58 omega,
59 len,
60 "Omega (pressure vertical velocity)",
61 ));
62 err_return.push_error(validate_vector_len(height, len, "Height"));
63 err_return.push_error(validate_vector_len(cloud_fraction, len, "Cloud fraction"));
64
65 err_return.push_error(check_vertical_height_pressure(snd));
69
70 check_temp_wet_bulb_dew_point(snd, &mut err_return);
72
73 for wind_val in wind {
75 if let Some(WindSpdDir {
76 speed: spd,
77 direction: dir,
78 }) = wind_val.into_option()
79 {
80 validate_f64_positive!(some(spd), "Wind speed", err_return);
81 validate_wind_direction!(dir, err_return);
82 }
83 }
84
85 for cld in cloud_fraction {
87 validate_f64_positive!(*cld, "Cloud fraction", err_return);
88 }
89
90 validate_f64_positive!(snd.low_cloud(), "Low cloud", err_return);
93 validate_f64_positive!(snd.mid_cloud(), "Mid cloud", err_return);
94 validate_f64_positive!(snd.high_cloud(), "Hi cloud", err_return);
95
96 if let Some(WindSpdDir {
97 speed: spd,
98 direction: dir,
99 }) = snd.sfc_wind().into_option()
100 {
101 validate_f64_positive!(some(spd), "Wind speed", err_return);
102 validate_wind_direction!(dir, err_return);
103 }
104
105 validate_f64_positive!(snd.mslp(), "MSLP", err_return);
106
107 validate_f64_positive!(snd.station_pressure(), "Station pressure", err_return);
108
109 err_return.check_any()
110}
111
112fn check_pressure_exists(pressure: &[Optioned<HectoPascal>]) -> Result<(), ValidationError> {
113 if pressure.is_empty() {
114 Err(ValidationError::NoPressureProfile)
115 } else {
116 Ok(())
117 }
118}
119
120fn validate_vector_len<T>(
121 vec: &[T],
122 len: usize,
123 var_name: &'static str,
124) -> Result<(), ValidationError> {
125 if !vec.is_empty() && vec.len() != len {
126 Err(ValidationError::InvalidVectorLength(
127 var_name,
128 vec.len(),
129 len,
130 ))
131 } else {
132 Ok(())
133 }
134}
135
136fn check_vertical_height_pressure(snd: &Sounding) -> Result<(), ValidationError> {
137 let pressure = snd
140 .pressure_profile()
141 .into_iter()
142 .filter_map(|val| val.into_option())
143 .map(|HectoPascal(val)| val);
144 let mut pressure_one_level_down = snd
145 .station_pressure()
146 .map_t(|HectoPascal(val)| val)
147 .unwrap_or(::std::f64::MAX);
148 for pres in pressure {
149 if pressure_one_level_down < pres {
150 return Err(ValidationError::PressureNotDecreasingWithHeight);
151 }
152 pressure_one_level_down = pres;
153 }
154
155 let height = snd
157 .height_profile()
158 .into_iter()
159 .filter_map(|val| val.into_option())
160 .map(|Meters(val)| val);
161 let mut height_one_level_down = snd
162 .station_info()
163 .elevation()
164 .map(|Meters(val)| val)
165 .unwrap_or(::std::f64::MIN);
166 for hght in height {
167 if height_one_level_down > hght {
168 return Err(ValidationError::PressureNotDecreasingWithHeight);
169 }
170 height_one_level_down = hght;
171 }
172
173 Ok(())
174}
175
176fn check_temp_wet_bulb_dew_point(snd: &Sounding, ve: &mut ValidationErrors) {
177 let temperature = snd.temperature_profile();
178 let wet_bulb = snd.wet_bulb_profile();
179 let dew_point = snd.dew_point_profile();
180
181 for (t, wb) in temperature.iter().zip(wet_bulb.iter()) {
183 if let (Some(Celsius(t)), Some(Celsius(wb))) = (t.into_option(), wb.into_option()) {
184 if t < wb {
185 ve.push_error(Err(ValidationError::TemperatureLessThanWetBulb(t, wb)));
186 }
187 }
188 }
189 for (t, dp) in temperature.iter().zip(dew_point.iter()) {
190 if let (Some(Celsius(t)), Some(Celsius(dp))) = (t.into_option(), dp.into_option()) {
191 if t < dp {
192 ve.push_error(Err(ValidationError::TemperatureLessThanDewPoint(t, dp)));
193 }
194 }
195 }
196 for (wb, dp) in wet_bulb.iter().zip(dew_point.iter()) {
197 if let (Some(Celsius(wb)), Some(Celsius(dp))) = (wb.into_option(), dp.into_option()) {
198 if wb < dp {
199 ve.push_error(Err(ValidationError::WetBulbLessThanDewPoint(wb, dp)));
200 }
201 }
202 }
203}