nexrad_data/volume/
record.rs

1use std::fmt::Debug;
2
3#[derive(Clone, PartialEq, Eq, Hash)]
4enum RecordData<'a> {
5    Borrowed(&'a [u8]),
6    Owned(Vec<u8>),
7}
8
9impl Debug for RecordData<'_> {
10    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
11        match self {
12            RecordData::Borrowed(data) => write!(f, "RecordData::Borrowed({} bytes)", data.len()),
13            RecordData::Owned(data) => write!(f, "RecordData::Owned({} bytes)", data.len()),
14        }
15    }
16}
17
18/// Represents a single LDM record with its data which may be compressed.
19///
20/// The Unidata Local Data Manager (LDM) is a data distribution system used by the NWS to distribute
21/// NEXRAD archival radar data. A NEXRAD "Archive II" file starts with an
22/// [crate::volume::Header] followed by a series of compressed LDM records, each
23/// containing messages with radar data.
24#[derive(Clone, PartialEq, Eq, Hash)]
25pub struct Record<'a>(RecordData<'a>);
26
27impl<'a> Record<'a> {
28    /// Creates a new LDM record with the provided data.
29    pub fn new(data: Vec<u8>) -> Self {
30        Record(RecordData::Owned(data))
31    }
32
33    /// Creates a new LDM record with the provided data slice.
34    pub fn from_slice(data: &'a [u8]) -> Self {
35        Record(RecordData::Borrowed(data))
36    }
37
38    /// The data contained in this LDM record.
39    pub fn data(&self) -> &[u8] {
40        match &self.0 {
41            RecordData::Borrowed(data) => data,
42            RecordData::Owned(data) => data,
43        }
44    }
45
46    /// Whether this LDM record's data is compressed.
47    pub fn compressed(&self) -> bool {
48        self.data().len() >= 6 && self.data()[4..6].as_ref() == b"BZ"
49    }
50
51    /// Decompresses this LDM record's data.
52    #[cfg(feature = "bzip2")]
53    pub fn decompress<'b>(&self) -> crate::result::Result<Record<'b>> {
54        use crate::result::Error;
55        use bzip2::read::BzDecoder;
56        use std::io::Read;
57
58        if !self.compressed() {
59            return Err(Error::UncompressedDataError);
60        }
61
62        // Skip the four-byte record size prefix
63        let data = self.data().split_at(4).1;
64
65        let mut decompressed_data = Vec::new();
66        BzDecoder::new(data).read_to_end(&mut decompressed_data)?;
67
68        Ok(Record::new(decompressed_data))
69    }
70
71    /// Decodes the NEXRAD level II messages contained in this LDM record.
72    #[cfg(feature = "decode")]
73    pub fn messages(&self) -> crate::result::Result<Vec<nexrad_decode::messages::Message>> {
74        use crate::result::Error;
75        use nexrad_decode::messages::decode_messages;
76        use std::io::Cursor;
77
78        if self.compressed() {
79            return Err(Error::CompressedDataError);
80        }
81
82        let mut reader = Cursor::new(self.data());
83        Ok(decode_messages(&mut reader)?)
84    }
85}
86
87impl Debug for Record<'_> {
88    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
89        let mut debug = f.debug_struct("Record");
90        debug.field("data.len()", &self.data().len());
91        debug.field(
92            "borrowed",
93            match &self.0 {
94                RecordData::Borrowed(_) => &true,
95                RecordData::Owned(_) => &false,
96            },
97        );
98        debug.field("compressed", &self.compressed());
99
100        #[cfg(feature = "decode")]
101        debug.field(
102            "messages.len()",
103            &self.messages().map(|messages| messages.len()),
104        );
105
106        debug.finish()
107    }
108}
109
110/// Splits compressed LDM record data into individual records. Will omit the record size prefix from
111/// each record.
112pub fn split_compressed_records(data: &[u8]) -> Vec<Record> {
113    let mut records = Vec::new();
114
115    let mut position = 0;
116    loop {
117        if position >= data.len() {
118            break;
119        }
120
121        let mut record_size = [0; 4];
122        record_size.copy_from_slice(&data[position..position + 4]);
123        let record_size = i32::from_be_bytes(record_size).unsigned_abs() as usize;
124
125        records.push(Record::from_slice(
126            &data[position..position + record_size + 4],
127        ));
128        position += record_size + 4;
129    }
130
131    records
132}