nexrad_decode/
summarize.rs

1use 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/// Summary of a set of messages.
8#[derive(Clone, PartialEq)]
9pub struct MessageSummary {
10    /// The distinct volume coverage patterns found in these messages.
11    pub volume_coverage_patterns: HashSet<digital_radar_data::VolumeCoveragePattern>,
12
13    /// The number of messages of each type in the order they appear. Multiple messages of the same
14    /// type will be grouped together if consecutive.
15    pub message_types: Vec<(MessageType, usize)>,
16
17    /// Summaries of each scan found in these messages.
18    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/// Summary of a single scan.
45#[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    /// The number of messages containing a given radar data type.
56    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
80/// Provides a summary of the given messages.
81pub 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}