1use crate::result::{Error, Result};
2use crate::ScanDerivedProduct;
3use nexrad_model::data::{CartesianField, GateStatus, Scan, SweepField};
4use nexrad_model::geo::{GeoExtent, GeoPoint, RadarCoordinateSystem};
5
6pub struct CompositeReflectivity;
30
31impl ScanDerivedProduct for CompositeReflectivity {
32 fn name(&self) -> &str {
33 "CompositeReflectivity"
34 }
35
36 fn compute(
37 &self,
38 _scan: &Scan,
39 fields: &[SweepField],
40 coord_system: &RadarCoordinateSystem,
41 output_extent: &GeoExtent,
42 output_resolution: (usize, usize),
43 ) -> Result<CartesianField> {
44 if fields.is_empty() {
45 return Err(Error::MissingData("no sweep fields provided".to_string()));
46 }
47
48 let (width, height) = output_resolution;
49 if width == 0 || height == 0 {
50 return Err(Error::InvalidParameter(
51 "output resolution must be > 0".to_string(),
52 ));
53 }
54
55 let unit = fields[0].unit().to_string();
56 let mut output = CartesianField::new(
57 "Composite Reflectivity",
58 &unit,
59 *output_extent,
60 width,
61 height,
62 );
63
64 let lat_range = output_extent.max.latitude - output_extent.min.latitude;
65 let lon_range = output_extent.max.longitude - output_extent.min.longitude;
66
67 for row in 0..height {
68 let lat = output_extent.max.latitude - (row as f64 + 0.5) / height as f64 * lat_range;
70
71 for col in 0..width {
72 let lon =
73 output_extent.min.longitude + (col as f64 + 0.5) / width as f64 * lon_range;
74
75 let geo_point = GeoPoint {
76 latitude: lat,
77 longitude: lon,
78 };
79
80 let mut max_value = f32::MIN;
81 let mut found_valid = false;
82
83 for field in fields {
84 let polar = coord_system.geo_to_polar(geo_point, field.elevation_degrees());
85
86 if let Some((val, status)) =
87 field.value_at_polar(polar.azimuth_degrees, polar.range_km)
88 {
89 if status == GateStatus::Valid && val > max_value {
90 max_value = val;
91 found_valid = true;
92 }
93 }
94 }
95
96 if found_valid {
97 output.set(row, col, max_value, GateStatus::Valid);
98 }
99 }
100 }
101
102 Ok(output)
103 }
104}
105
106#[cfg(test)]
107mod tests {
108 use super::*;
109 use nexrad_model::data::{PulseWidth, Scan, SweepField, VolumeCoveragePattern};
110 use nexrad_model::geo::RadarCoordinateSystem;
111 use nexrad_model::meta::Site;
112
113 fn test_site() -> Site {
114 Site::new(*b"KTLX", 35.3331, -97.2778, 370, 10)
115 }
116
117 fn test_scan() -> Scan {
118 let vcp = VolumeCoveragePattern::new(
119 215,
120 1,
121 0.5,
122 PulseWidth::Short,
123 false,
124 0,
125 false,
126 0,
127 false,
128 false,
129 0,
130 false,
131 false,
132 vec![],
133 );
134 Scan::new(vcp, vec![])
135 }
136
137 fn make_uniform_field(elevation: f32, value: f32) -> SweepField {
138 let mut azimuths = Vec::new();
139 for i in 0..360 {
140 azimuths.push(i as f32);
141 }
142
143 let gate_count = 100;
144 let mut field = SweepField::new_empty(
145 "Reflectivity",
146 "dBZ",
147 elevation,
148 azimuths,
149 1.0,
150 2.0,
151 0.25,
152 gate_count,
153 );
154
155 for az in 0..360 {
156 for gate in 0..gate_count {
157 field.set(az, gate, value, GateStatus::Valid);
158 }
159 }
160
161 field
162 }
163
164 #[test]
165 fn test_cref_single_tilt() {
166 let coord_sys = RadarCoordinateSystem::new(&test_site());
167 let extent = coord_sys.sweep_extent(25.0);
168 let scan = test_scan();
169
170 let fields = vec![make_uniform_field(0.5, 30.0)];
171
172 let result = CompositeReflectivity
173 .compute(&scan, &fields, &coord_sys, &extent, (10, 10))
174 .unwrap();
175
176 let (val, status) = result.get(5, 5);
178 assert_eq!(status, GateStatus::Valid);
179 assert_eq!(val, 30.0);
180 }
181
182 #[test]
183 fn test_cref_takes_max() {
184 let coord_sys = RadarCoordinateSystem::new(&test_site());
185 let extent = coord_sys.sweep_extent(25.0);
186 let scan = test_scan();
187
188 let fields = vec![
189 make_uniform_field(0.5, 20.0),
190 make_uniform_field(1.5, 40.0),
191 make_uniform_field(3.5, 25.0),
192 ];
193
194 let result = CompositeReflectivity
195 .compute(&scan, &fields, &coord_sys, &extent, (10, 10))
196 .unwrap();
197
198 let (val, status) = result.get(5, 5);
200 assert_eq!(status, GateStatus::Valid);
201 assert_eq!(val, 40.0);
202 }
203
204 #[test]
205 fn test_cref_empty_fields_error() {
206 let coord_sys = RadarCoordinateSystem::new(&test_site());
207 let extent = coord_sys.sweep_extent(25.0);
208 let scan = test_scan();
209
210 let result = CompositeReflectivity.compute(&scan, &[], &coord_sys, &extent, (10, 10));
211 assert!(result.is_err());
212 }
213}