Skip to main content

nexrad_decode/messages/digital_radar_data/
generic_data_block.rs

1use crate::messages::digital_radar_data::{ControlFlags, DataBlockId, ScaledMomentValue};
2use crate::messages::primitive_aliases::{
3    Code1, Integer1, Integer2, Integer4, Real4, ScaledInteger2,
4};
5use serde::Deserialize;
6use std::fmt::Debug;
7
8#[cfg(feature = "uom")]
9use uom::si::f64::{Information, Length};
10#[cfg(feature = "uom")]
11use uom::si::information::byte;
12#[cfg(feature = "uom")]
13use uom::si::length::kilometer;
14
15/// A generic data moment block.
16#[derive(Clone, PartialEq)]
17pub struct GenericDataBlock {
18    /// The generic data block's header information.
19    pub header: GenericDataBlockHeader,
20
21    /// The generic data block's encoded moment data.
22    pub encoded_data: Vec<u8>,
23}
24
25impl GenericDataBlock {
26    /// Creates a new generic data moment block from the decoded header.
27    pub(crate) fn new(header: GenericDataBlockHeader) -> Self {
28        let word_size_bytes = header.data_word_size as usize / 8;
29        let encoded_data_size = header.number_of_data_moment_gates as usize * word_size_bytes;
30        Self {
31            encoded_data: vec![0; encoded_data_size],
32            header,
33        }
34    }
35
36    /// Raw gate values for this moment/radial ordered in ascending distance from the radar. These
37    /// values are stored in a fixed-point representation using the `DataMomentHeader.offset` and
38    /// `DataMomentHeader.scale` fields. `decoded_data` provides decoded floating-point values.  
39    pub fn encoded_values(&self) -> &[u8] {
40        &self.encoded_data
41    }
42
43    /// Decodes raw moment values from `encoded_data` from their fixed-point representation into
44    /// their floating point representation. Additionally, identifies special values such as "below
45    /// threshold" and "range folded".
46    pub fn decoded_values(&self) -> Vec<ScaledMomentValue> {
47        self.encoded_data
48            .iter()
49            .copied()
50            .map(|raw_value| {
51                if self.header.scale == 0.0 {
52                    return ScaledMomentValue::Value(raw_value as f32);
53                }
54
55                match raw_value {
56                    0 => ScaledMomentValue::BelowThreshold,
57                    1 => ScaledMomentValue::RangeFolded,
58                    _ => ScaledMomentValue::Value(
59                        (raw_value as f32 - self.header.offset) / self.header.scale,
60                    ),
61                }
62            })
63            .collect()
64    }
65
66    /// Get moment data from this generic data block. Note that this will clone the underlying data.
67    #[cfg(feature = "nexrad-model")]
68    pub fn moment_data(&self) -> nexrad_model::data::MomentData {
69        nexrad_model::data::MomentData::from_fixed_point(
70            self.header.scale,
71            self.header.offset,
72            self.encoded_data.clone(),
73        )
74    }
75
76    /// Convert this generic data block into common model moment data, minimizing data copies.
77    #[cfg(feature = "nexrad-model")]
78    pub fn into_moment_data(self) -> nexrad_model::data::MomentData {
79        nexrad_model::data::MomentData::from_fixed_point(
80            self.header.scale,
81            self.header.offset,
82            self.encoded_data,
83        )
84    }
85}
86
87impl Debug for GenericDataBlock {
88    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
89        f.debug_struct("GenericDataBlock")
90            .field("header", &self.header)
91            .field("data", &self.encoded_data.len())
92            .finish()
93    }
94}
95
96/// A generic data moment block's decoded header.
97#[derive(Clone, PartialEq, Deserialize)]
98pub struct GenericDataBlockHeader {
99    /// Data block identifier.
100    pub data_block_id: DataBlockId,
101
102    /// Reserved.
103    pub reserved: Integer4,
104
105    /// Number of data moment gates for current radial, from 0 to 1840.
106    pub number_of_data_moment_gates: Integer2,
107
108    /// Range to center of first range gate in 0.000-scaled kilometers.
109    pub data_moment_range: ScaledInteger2,
110
111    /// Size of data moment sample interval in 0.000-scaled kilometers from 0.25 to 4.0.
112    pub data_moment_range_sample_interval: ScaledInteger2,
113
114    /// Threshold parameter specifying the minimum difference in echo power between two resolution
115    /// gates in dB for them to not be labeled as "overlayed".
116    pub tover: ScaledInteger2,
117
118    /// Signal-to-noise ratio threshold for valid data from -12 to 20 dB.
119    pub snr_threshold: ScaledInteger2,
120
121    /// Flags indicating special control features.
122    ///
123    /// Flags:
124    ///   0 = None
125    ///   1 = Recombined azimuthal radials
126    ///   2 = Recombined range gates
127    ///   3 = Recombined radials and range gates to legacy resolution
128    pub control_flags: Code1,
129
130    /// Number of bits (8 or 16) used for storing data for each data moment gate.
131    pub data_word_size: Integer1,
132
133    /// Scale factor for converting data moments to floating-point representation.
134    pub scale: Real4,
135
136    /// Offset value for converting data moments to floating-point representation.
137    pub offset: Real4,
138}
139
140impl GenericDataBlockHeader {
141    /// Range to center of first range gate.
142    #[cfg(feature = "uom")]
143    pub fn data_moment_range(&self) -> Length {
144        Length::new::<kilometer>(self.data_moment_range as f64 * 0.001)
145    }
146
147    /// Size of data moment sample interval.
148    #[cfg(feature = "uom")]
149    pub fn data_moment_range_sample_interval(&self) -> Length {
150        Length::new::<kilometer>(self.data_moment_range_sample_interval as f64 * 0.001)
151    }
152
153    /// Flags indicating special control features.
154    pub fn control_flags(&self) -> ControlFlags {
155        match self.control_flags {
156            0 => ControlFlags::None,
157            1 => ControlFlags::RecombinedAzimuthalRadials,
158            2 => ControlFlags::RecombinedRangeGates,
159            3 => ControlFlags::RecombinedRadialsAndRangeGatesToLegacyResolution,
160            _ => panic!("Invalid control flag value: {}", self.control_flags),
161        }
162    }
163
164    /// Size of the data moment block in bytes.
165    #[cfg(feature = "uom")]
166    pub fn moment_size(&self) -> Information {
167        Information::new::<byte>(
168            self.number_of_data_moment_gates as f64 * self.data_word_size as f64 / 8.0,
169        )
170    }
171}
172
173#[cfg(not(feature = "uom"))]
174impl Debug for GenericDataBlockHeader {
175    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
176        f.debug_struct("GenericDataBlockHeader")
177            .field("data_block_id", &self.data_block_id)
178            .field("reserved", &self.reserved)
179            .field(
180                "number_of_data_moment_gates",
181                &self.number_of_data_moment_gates,
182            )
183            .field("data_moment_range", &self.data_moment_range)
184            .field(
185                "data_moment_range_sample_interval",
186                &self.data_moment_range_sample_interval,
187            )
188            .field("tover", &self.tover)
189            .field("snr_threshold", &self.snr_threshold)
190            .field("control_flags", &self.control_flags())
191            .field("data_word_size", &self.data_word_size)
192            .field("scale", &self.scale)
193            .field("offset", &self.offset)
194            .finish()
195    }
196}
197
198#[cfg(feature = "uom")]
199impl Debug for GenericDataBlockHeader {
200    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
201        f.debug_struct("GenericDataBlockHeader")
202            .field("data_block_id", &self.data_block_id)
203            .field("reserved", &self.reserved)
204            .field(
205                "number_of_data_moment_gates",
206                &self.number_of_data_moment_gates,
207            )
208            .field("data_moment_range", &self.data_moment_range())
209            .field(
210                "data_moment_range_sample_interval",
211                &self.data_moment_range_sample_interval(),
212            )
213            .field("tover", &self.tover)
214            .field("snr_threshold", &self.snr_threshold)
215            .field("control_flags", &self.control_flags())
216            .field("data_word_size", &self.data_word_size)
217            .field("scale", &self.scale)
218            .field("offset", &self.offset)
219            .finish()
220    }
221}