wxbox/crates/ar2/src/lib.rs
core fe4a47d6cc
Some checks are pending
Verify Latest Dependencies / Verify Latest Dependencies (push) Waiting to run
build and test / wxbox - latest (push) Waiting to run
further optimization
2025-05-19 10:32:19 -04:00

210 lines
No EOL
6.5 KiB
Rust

use nexrad_data::volume::File;
use nexrad_decode::messages::MessageContents;
use nexrad_decode::messages::digital_radar_data::{GenericDataBlock, RadialStatus};
use nexrad_decode::result::Error;
use std::fmt::Debug;
use rayon::prelude::*;
pub mod sites;
pub struct Scan {
pub coverage_pattern_number: u16,
pub sweeps: Vec<Sweep>,
}
pub struct Sweep {
pub elevation_number: u8,
pub radials: Vec<Radial>,
}
pub struct Radial {
pub collection_timestamp: i64,
pub azimuth_number: u16,
pub azimuth_angle_degrees: f32,
pub azimuth_spacing_degrees: f32,
pub radial_status: RadialStatus,
pub elevation_number: u8,
pub elevation_number_degrees: f32,
pub reflectivity: Option<MomentData>,
pub velocity: Option<MomentData>,
pub spectrum_width: Option<MomentData>,
pub differential_reflectivity: Option<MomentData>,
pub differential_phase: Option<MomentData>,
pub correlation_coefficient: Option<MomentData>,
pub specific_differential_phase: Option<MomentData>,
}
#[derive(Debug)]
pub struct MomentData {
pub scale: f32,
pub offset: f32,
pub values: Vec<u8>,
pub start_range: u16,
pub sample_interval: u16,
}
impl MomentData {
/// Values from this data moment corresponding to gates in the radial.
pub fn values(&self) -> Vec<MomentValue> {
let copied_values = self.values.iter().copied();
if self.scale == 0.0 {
return copied_values
.map(|raw_value| MomentValue::Value(raw_value as f32))
.collect();
}
copied_values
.map(|raw_value| match raw_value {
0 => MomentValue::BelowThreshold,
1 => MomentValue::RangeFolded,
_ => MomentValue::Value((raw_value as f32 - self.offset) / self.scale),
})
.collect()
}
}
/// The data moment value for a product in a radial's gate. The value may be a floating-point number
/// or a special case such as "below threshold" or "range folded".
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum MomentValue {
/// The data moment value for a gate.
Value(f32),
/// The value for this gate was below the signal threshold.
BelowThreshold,
/// The value for this gate exceeded the maximum unambiguous range.
RangeFolded,
}
pub fn parse(input: Vec<u8>) -> nexrad_data::result::Result<Scan> {
let file = File::new(input);
let mut vcp: Option<u16> = None;
let mut radials = vec![];
let mut radial_chunks: Vec<nexrad_data::result::Result<(Vec<Radial>, Option<u16>)>> = file.records()
.par_iter_mut()
.map(|record| {
let mut vcp = None;
let mut radials = vec![];
if record.compressed() {
*record = record.decompress()?; // this specifically seems to be most problematic. bzip is SLOW
}
let messages = record.messages()?;
for message in messages {
let contents = message.contents();
if let MessageContents::DigitalRadarData(radar_data_message) = contents {
if vcp.is_none() {
if let Some(volume_block) = &radar_data_message.volume_data_block {
vcp = Some(volume_block.volume_coverage_pattern_number);
}
}
radials.push(into_radial(*radar_data_message.clone())?)
}
}
Ok((radials, vcp))
})
.collect();
for radial_chunk in radial_chunks {
let (mut radial_chunk, this_vcp) = radial_chunk?;
if vcp.is_none() && this_vcp.is_some() {
vcp = this_vcp;
}
radials.append(&mut radial_chunk);
}
Ok(Scan {
coverage_pattern_number: vcp.ok_or(Error::DecodingError("no vcp".to_string()))?,
sweeps: Sweep::from_radials(radials),
})
}
#[inline]
fn into_radial(
message: nexrad_decode::messages::digital_radar_data::Message,
) -> nexrad_data::result::Result<Radial> {
Ok(Radial {
collection_timestamp: message
.header
.date_time()
.ok_or(Error::MessageMissingDateError)?
.timestamp_millis(),
azimuth_number: message.header.azimuth_number,
azimuth_angle_degrees: message.header.azimuth_angle,
azimuth_spacing_degrees: message.header.azimuth_resolution_spacing as f32 * 0.5,
radial_status: message.header.radial_status(),
elevation_number: message.header.elevation_number,
elevation_number_degrees: message.header.elevation_angle,
reflectivity: message.reflectivity_data_block.map(|u| into_moment_data(u)),
velocity: message.velocity_data_block.map(|u| into_moment_data(u)),
spectrum_width: message
.spectrum_width_data_block
.map(|u| into_moment_data(u)),
differential_reflectivity: message
.differential_reflectivity_data_block
.map(|u| into_moment_data(u)),
differential_phase: message
.differential_phase_data_block
.map(|u| into_moment_data(u)),
correlation_coefficient: message
.correlation_coefficient_data_block
.map(|u| into_moment_data(u)),
specific_differential_phase: message
.specific_diff_phase_data_block
.map(|u| into_moment_data(u)),
})
}
#[inline(always)]
fn into_moment_data(block: GenericDataBlock) -> MomentData {
MomentData {
scale: block.header.scale,
offset: block.header.offset,
values: block.encoded_data,
start_range: block.header.data_moment_range,
sample_interval: block.header.data_moment_range_sample_interval,
}
}
impl Sweep {
pub fn new(elevation_number: u8, radials: Vec<Radial>) -> Self {
Self {
elevation_number,
radials,
}
}
pub fn from_radials(radials: Vec<Radial>) -> Vec<Self> {
let mut sweeps = Vec::new();
let mut sweep_elevation_number = None;
let mut sweep_radials = Vec::new();
for radial in radials {
if let Some(elevation_number) = sweep_elevation_number {
if elevation_number != radial.elevation_number {
sweeps.push(Sweep::new(elevation_number, sweep_radials));
sweep_radials = Vec::new();
}
}
sweep_elevation_number = Some(radial.elevation_number);
sweep_radials.push(radial);
}
sweeps
}
}