nexrad_decode/
summarize.rs1use crate::messages::digital_radar_data;
2use crate::messages::{Message, MessageContents, MessageType};
3use chrono::{DateTime, Utc};
4use std::collections::{HashMap, HashSet};
5use std::fmt::Debug;
6
7#[derive(Clone, PartialEq)]
9pub struct MessageSummary {
10 pub volume_coverage_patterns: HashSet<digital_radar_data::VolumeCoveragePattern>,
12
13 pub message_types: Vec<(MessageType, usize)>,
16
17 pub scans: Vec<ScanSummary>,
19
20 pub earliest_collection_time: Option<DateTime<Utc>>,
21 pub latest_collection_time: Option<DateTime<Utc>>,
22}
23
24impl Debug for MessageSummary {
25 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
26 let mut debug = f.debug_struct("MessageSummary");
27 debug.field("volume_coverage_patterns", &self.volume_coverage_patterns);
28
29 let message_types_string = self
30 .message_types
31 .iter()
32 .map(|(k, v)| format!("{:?}: {}", k, v))
33 .collect::<Vec<_>>();
34
35 debug.field("message_types", &message_types_string);
36
37 debug.field("scans", &self.scans);
38 debug.field("earliest_collection_time", &self.earliest_collection_time);
39 debug.field("latest_collection_time", &self.latest_collection_time);
40 debug.finish()
41 }
42}
43
44#[derive(Clone, PartialEq)]
46pub struct ScanSummary {
47 pub start_time: Option<DateTime<Utc>>,
48 pub end_time: Option<DateTime<Utc>>,
49
50 pub elevation: u8,
51
52 pub start_azimuth: f32,
53 pub end_azimuth: f32,
54
55 pub data_types: HashMap<String, usize>,
57}
58
59impl Debug for ScanSummary {
60 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
61 let mut debug = f.debug_struct("ScanSummary");
62 debug.field("start_time", &self.start_time);
63 debug.field("end_time", &self.end_time);
64 debug.field("elevation", &self.elevation);
65 debug.field("start_azimuth", &self.start_azimuth);
66 debug.field("end_azimuth", &self.end_azimuth);
67
68 let data_types_string = self
69 .data_types
70 .iter()
71 .map(|(k, v)| format!("{}: {}", k, v))
72 .collect::<Vec<_>>();
73
74 debug.field("data_types", &data_types_string);
75
76 debug.finish()
77 }
78}
79
80pub fn messages(messages: &[Message]) -> MessageSummary {
82 let mut summary = MessageSummary {
83 volume_coverage_patterns: HashSet::new(),
84 message_types: Vec::new(),
85 scans: Vec::new(),
86 earliest_collection_time: None,
87 latest_collection_time: None,
88 };
89
90 if let Some(first_message) = messages.first() {
91 summary.earliest_collection_time = first_message.header().date_time();
92 }
93
94 let mut scan_summary = None;
95 for message in messages {
96 process_message(&mut summary, &mut scan_summary, message);
97 }
98
99 if let Some(scan_summary) = scan_summary.take() {
100 summary.scans.push(scan_summary);
101 }
102
103 summary
104}
105
106fn process_message(
107 summary: &mut MessageSummary,
108 scan_summary: &mut Option<ScanSummary>,
109 message: &Message,
110) {
111 let message_type = message.header().message_type();
112 if let Some((last_message_type, count)) = summary.message_types.last_mut() {
113 if *last_message_type == message_type {
114 *count += 1;
115 } else {
116 summary.message_types.push((message_type, 1));
117 }
118 } else {
119 summary.message_types.push((message_type, 1));
120 }
121
122 match message.contents() {
123 MessageContents::DigitalRadarData(radar_data_message) => {
124 process_digital_radar_data_message(summary, scan_summary, radar_data_message);
125 return;
126 }
127 _ => {}
128 }
129
130 if let Some(scan_summary) = scan_summary.take() {
131 summary.scans.push(scan_summary);
132 }
133}
134
135fn process_digital_radar_data_message(
136 summary: &mut MessageSummary,
137 scan_summary: &mut Option<ScanSummary>,
138 message: &digital_radar_data::Message,
139) {
140 let elevation_changed =
141 |summary: &mut ScanSummary| summary.elevation != message.header.elevation_number;
142
143 if let Some(scan_summary) = scan_summary.take_if(elevation_changed) {
144 summary.scans.push(scan_summary);
145 }
146
147 let scan_summary = scan_summary.get_or_insert_with(|| ScanSummary {
148 start_time: message.header.date_time(),
149 end_time: message.header.date_time(),
150 elevation: message.header.elevation_number,
151 start_azimuth: message.header.azimuth_angle,
152 end_azimuth: message.header.azimuth_angle,
153 data_types: HashMap::new(),
154 });
155
156 if message.header.date_time().is_some() {
157 if summary.earliest_collection_time.is_none()
158 || summary.earliest_collection_time > message.header.date_time()
159 {
160 summary.earliest_collection_time = message.header.date_time();
161 }
162
163 if summary.latest_collection_time.is_none()
164 || summary.latest_collection_time < message.header.date_time()
165 {
166 summary.latest_collection_time = message.header.date_time();
167 }
168
169 if scan_summary.start_time.is_none() || scan_summary.start_time > message.header.date_time()
170 {
171 scan_summary.start_time = message.header.date_time();
172 }
173
174 if scan_summary.end_time.is_none() || scan_summary.end_time < message.header.date_time() {
175 scan_summary.end_time = message.header.date_time();
176 }
177 }
178
179 if let Some(volume_data) = &message.volume_data_block {
180 summary
181 .volume_coverage_patterns
182 .insert(volume_data.volume_coverage_pattern());
183 }
184
185 scan_summary.end_azimuth = message.header.azimuth_angle;
186
187 let mut increment_count = |data_type: &str| {
188 let count = scan_summary.data_types.get(data_type).unwrap_or(&0) + 1;
189 scan_summary.data_types.insert(data_type.to_string(), count);
190 };
191
192 if message.reflectivity_data_block.is_some() {
193 increment_count("Reflectivity");
194 }
195 if message.velocity_data_block.is_some() {
196 increment_count("Velocity");
197 }
198 if message.spectrum_width_data_block.is_some() {
199 increment_count("Spectrum Width");
200 }
201 if message.differential_reflectivity_data_block.is_some() {
202 increment_count("Differential Reflectivity");
203 }
204 if message.differential_phase_data_block.is_some() {
205 increment_count("Differential Phase");
206 }
207 if message.correlation_coefficient_data_block.is_some() {
208 increment_count("Correlation Coefficient");
209 }
210 if message.specific_diff_phase_data_block.is_some() {
211 increment_count("Specific Differential Phase");
212 }
213}