work
This commit is contained in:
parent
a729956898
commit
4b7ec8c058
|
@ -21,6 +21,7 @@ log = "0.4"
|
||||||
wasm-logger = "0.2"
|
wasm-logger = "0.2"
|
||||||
serde-wasm-bindgen = "0.6"
|
serde-wasm-bindgen = "0.6"
|
||||||
chrono = "0.4"
|
chrono = "0.4"
|
||||||
|
itertools = "0.11"
|
||||||
|
|
||||||
[dependencies.js-sys]
|
[dependencies.js-sys]
|
||||||
version = "0.3"
|
version = "0.3"
|
||||||
|
|
|
@ -1,13 +1,16 @@
|
||||||
use web_sys::FileReader;
|
use web_sys::FileReader;
|
||||||
use crate::loadar2;
|
use crate::loadar2;
|
||||||
|
use crate::mode::Mode;
|
||||||
use crate::rendering::render;
|
use crate::rendering::render;
|
||||||
use crate::scope::ScopeState;
|
use crate::scope::ScopeState;
|
||||||
|
|
||||||
pub fn should_newline(state: &mut ScopeState) -> bool {
|
pub fn should_newline(state: &mut ScopeState) -> bool {
|
||||||
state.command_buf.starts_with("SET MODE")
|
state.command_buf.starts_with("MODE SET")
|
||||||
|
|| state.command_buf.starts_with("ELEVATION SET")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn exec(state: &mut ScopeState, command: String) {
|
pub fn exec(state: &mut ScopeState, command: String) {
|
||||||
|
let tokens = command.split(' ').collect::<Vec<_>>();
|
||||||
if command == "CLF OV" {
|
if command == "CLF OV" {
|
||||||
state.file_input.click();
|
state.file_input.click();
|
||||||
} else if command == "CLF RELOAD" {
|
} else if command == "CLF RELOAD" {
|
||||||
|
@ -15,6 +18,67 @@ pub fn exec(state: &mut ScopeState, command: String) {
|
||||||
state.command_buf_response_mode = true;
|
state.command_buf_response_mode = true;
|
||||||
render(state).expect("rerender failed");
|
render(state).expect("rerender failed");
|
||||||
loadar2("file");
|
loadar2("file");
|
||||||
|
} else if command.starts_with("MODE SET") {
|
||||||
|
if tokens.len() < 3 {
|
||||||
|
state.command_buf = "ARGUMENT INVALID".to_string();
|
||||||
|
state.command_buf_response_mode = true;
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
let mode = tokens[2];
|
||||||
|
if let Some(ar2) = &state.ar2 {
|
||||||
|
let types = ar2.elevations.get(&state.selected_elevation).unwrap();
|
||||||
|
let valid_modes: Vec<_> = types[0].available_data.keys().collect();
|
||||||
|
|
||||||
|
if !valid_modes.contains(&&mode.to_string()) {
|
||||||
|
state.command_buf = "ARGUMENT INVALID".to_string();
|
||||||
|
state.command_buf_response_mode = true;
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
state.scope_mode = match mode {
|
||||||
|
"REF" => Mode::Reflectivity,
|
||||||
|
"VEL" => Mode::Velocity,
|
||||||
|
"SW" => Mode::SpectrumWidth,
|
||||||
|
"ZDR" => Mode::DifferentialReflectivity,
|
||||||
|
"PHI" => Mode::DifferentialPhase,
|
||||||
|
"RHO" => Mode::CorrelationCoefficient,
|
||||||
|
"CFP" => Mode::ClutterFilterPowerRemoved,
|
||||||
|
_ => unreachable!()
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
state.command_buf = "CANNOT SET MODE\nRADAR INOPERATIVE".to_string();
|
||||||
|
state.command_buf_response_mode = true;
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if command.starts_with("ELEVATION SET") {
|
||||||
|
if tokens.len() < 3 {
|
||||||
|
state.command_buf = "ARGUMENT INVALID".to_string();
|
||||||
|
state.command_buf_response_mode = true;
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
let elev = tokens[2].to_string();
|
||||||
|
if let Some(ar2) = &state.ar2 {
|
||||||
|
let mut valid_elevs = vec![];
|
||||||
|
for elev in ar2.elevations.keys() {
|
||||||
|
valid_elevs.push(format!("{:0>3}", elev));
|
||||||
|
}
|
||||||
|
if !valid_elevs.contains(&elev) {
|
||||||
|
state.command_buf = "ARGUMENT INVALID".to_string();
|
||||||
|
state.command_buf_response_mode = true;
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let number: usize = elev.parse().unwrap();
|
||||||
|
|
||||||
|
// reset scope back to reflectivity which is included in every elevation
|
||||||
|
state.scope_mode = Mode::Reflectivity;
|
||||||
|
state.selected_elevation = number;
|
||||||
|
} else {
|
||||||
|
state.command_buf = "CANNOT SET MODE\nRADAR INOPERATIVE".to_string();
|
||||||
|
state.command_buf_response_mode = true;
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
state.command_buf = "UNRECOGNIZED COMMAND".to_string();
|
state.command_buf = "UNRECOGNIZED COMMAND".to_string();
|
||||||
state.command_buf_response_mode = true;
|
state.command_buf_response_mode = true;
|
||||||
|
|
|
@ -15,6 +15,7 @@ use wasm_bindgen::prelude::*;
|
||||||
use web_sys::{CanvasRenderingContext2d, HtmlCanvasElement};
|
use web_sys::{CanvasRenderingContext2d, HtmlCanvasElement};
|
||||||
use crate::command::{exec, should_newline};
|
use crate::command::{exec, should_newline};
|
||||||
use crate::mode::Mode;
|
use crate::mode::Mode;
|
||||||
|
use crate::mode::Mode::Reflectivity;
|
||||||
use crate::scope::{Preferences, RenderState, ScopeState};
|
use crate::scope::{Preferences, RenderState, ScopeState};
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
|
@ -61,7 +62,8 @@ pub fn __nxrd_browser_init() -> AbiScopeState {
|
||||||
},
|
},
|
||||||
command_buf: String::new(),
|
command_buf: String::new(),
|
||||||
command_buf_response_mode: false,
|
command_buf_response_mode: false,
|
||||||
new_data_available: false
|
new_data_available: false,
|
||||||
|
selected_elevation: 1
|
||||||
};
|
};
|
||||||
|
|
||||||
info!("nexrad-browser initialized successfully");
|
info!("nexrad-browser initialized successfully");
|
||||||
|
@ -83,6 +85,8 @@ pub fn load_ar2(buf: &JsValue, scope: &mut AbiScopeState) {
|
||||||
info!("new data chunk loaded");
|
info!("new data chunk loaded");
|
||||||
scope.0.ar2 = Some(loaded);
|
scope.0.ar2 = Some(loaded);
|
||||||
scope.0.new_data_available = false;
|
scope.0.new_data_available = false;
|
||||||
|
scope.0.scope_mode = Reflectivity;
|
||||||
|
scope.0.command_buf = "NEW DATA LOADED SUCCESSFULLY".to_string();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
|
@ -92,7 +96,8 @@ pub fn new_file_available(scope: &mut AbiScopeState) {
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn keydown(state: &mut AbiScopeState, key: String) {
|
pub fn keydown(state: &mut AbiScopeState, key: String) {
|
||||||
if state.0.command_buf_response_mode {
|
if state.0.command_buf_response_mode && (key == "Backspace" || key.len() == 1) {
|
||||||
|
info!("resetting, key {} buf {}", key, state.0.command_buf);
|
||||||
state.0.command_buf = String::new();
|
state.0.command_buf = String::new();
|
||||||
state.0.command_buf_response_mode = false;
|
state.0.command_buf_response_mode = false;
|
||||||
}
|
}
|
||||||
|
@ -101,8 +106,8 @@ pub fn keydown(state: &mut AbiScopeState, key: String) {
|
||||||
state.0.command_buf = String::new();
|
state.0.command_buf = String::new();
|
||||||
} else if key == "Enter" {
|
} else if key == "Enter" {
|
||||||
let cmd = state.0.command_buf.clone();
|
let cmd = state.0.command_buf.clone();
|
||||||
exec(&mut state.0, cmd.replace('\n', " "));
|
|
||||||
state.0.command_buf = String::new();
|
state.0.command_buf = String::new();
|
||||||
|
exec(&mut state.0, cmd.replace('\n', " "));
|
||||||
} else if key == " " && should_newline(&mut state.0) {
|
} else if key == " " && should_newline(&mut state.0) {
|
||||||
state.0.command_buf += "\n";
|
state.0.command_buf += "\n";
|
||||||
} else if key == "Backspace" {
|
} else if key == "Backspace" {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
#[derive(Debug)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
pub enum Mode {
|
pub enum Mode {
|
||||||
/// REF
|
/// REF
|
||||||
Reflectivity,
|
Reflectivity,
|
||||||
|
@ -17,3 +17,42 @@ pub enum Mode {
|
||||||
/// RADR INOP
|
/// RADR INOP
|
||||||
RadarInoperative
|
RadarInoperative
|
||||||
}
|
}
|
||||||
|
impl Mode {
|
||||||
|
pub fn unselected(&self) -> &str {
|
||||||
|
match self {
|
||||||
|
Self::Reflectivity => " REF ",
|
||||||
|
Self::Velocity => " VEL ",
|
||||||
|
Self::SpectrumWidth => " SW ",
|
||||||
|
Self::DifferentialReflectivity => " ZDR ",
|
||||||
|
Self::DifferentialPhase => " PHI ",
|
||||||
|
Self::CorrelationCoefficient => " RHO ",
|
||||||
|
Self::ClutterFilterPowerRemoved => " CFP ",
|
||||||
|
Self::RadarInoperative => " RADR INOP"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn selected(&self) -> &str {
|
||||||
|
match self {
|
||||||
|
Self::Reflectivity => ">REF<",
|
||||||
|
Self::Velocity => ">VEL<",
|
||||||
|
Self::SpectrumWidth => ">SW <",
|
||||||
|
Self::DifferentialReflectivity => ">ZDR<",
|
||||||
|
Self::DifferentialPhase => ">PHI<",
|
||||||
|
Self::CorrelationCoefficient => ">RHO<",
|
||||||
|
Self::ClutterFilterPowerRemoved => ">CFP<",
|
||||||
|
Self::RadarInoperative => ">RADR INOP<"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn rname(&self) -> &str {
|
||||||
|
match self {
|
||||||
|
Self::Reflectivity => "REF",
|
||||||
|
Self::Velocity => "VEL",
|
||||||
|
Self::SpectrumWidth => "SW ",
|
||||||
|
Self::DifferentialReflectivity => "ZDR",
|
||||||
|
Self::DifferentialPhase => "PHI",
|
||||||
|
Self::CorrelationCoefficient => "RHO",
|
||||||
|
Self::ClutterFilterPowerRemoved => "CFP",
|
||||||
|
Self::RadarInoperative => "NOP"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,9 @@
|
||||||
use chrono::{DateTime, Timelike, Utc};
|
use chrono::{DateTime, Timelike, Utc};
|
||||||
|
use itertools::Itertools;
|
||||||
|
use log::info;
|
||||||
use wasm_bindgen::JsValue;
|
use wasm_bindgen::JsValue;
|
||||||
|
use crate::mode::Mode;
|
||||||
|
use crate::mode::Mode::{ClutterFilterPowerRemoved, CorrelationCoefficient, DifferentialPhase, DifferentialReflectivity, RadarInoperative, Reflectivity, SpectrumWidth, Velocity};
|
||||||
use crate::rescaleCanvas;
|
use crate::rescaleCanvas;
|
||||||
use crate::scope::ScopeState;
|
use crate::scope::ScopeState;
|
||||||
use crate::utils::parse_date;
|
use crate::utils::parse_date;
|
||||||
|
@ -9,6 +13,8 @@ pub const TEXT_COLOR_RED: &str = "#ef0000";
|
||||||
pub const TEXT_COLOR_GREEN: &str = "#4af626";
|
pub const TEXT_COLOR_GREEN: &str = "#4af626";
|
||||||
pub const TEXT_COLOR_WHITE: &str = "#dedede";
|
pub const TEXT_COLOR_WHITE: &str = "#dedede";
|
||||||
|
|
||||||
|
pub const RANGE: usize = 50;
|
||||||
|
|
||||||
pub fn time(h: usize, m: usize, s: usize, tag_end: &str) -> String {
|
pub fn time(h: usize, m: usize, s: usize, tag_end: &str) -> String {
|
||||||
format!("{:0>2}:{:0>2}:{:0>2}{}", h, m, s, tag_end)
|
format!("{:0>2}:{:0>2}:{:0>2}{}", h, m, s, tag_end)
|
||||||
}
|
}
|
||||||
|
@ -69,12 +75,92 @@ pub fn render(state: &mut ScopeState) -> Result<(), JsValue> {
|
||||||
ctx.fill_text(&format!("{} VCP {} {}", ar2.volume_header_record.icao, ar2.meta_rda_status_data.vcp, vcp_string(ar2.meta_rda_status_data.vcp)), (canvas.width() - 50) as f64, 50.0 + (state.prefs.fcs as f64))?;
|
ctx.fill_text(&format!("{} VCP {} {}", ar2.volume_header_record.icao, ar2.meta_rda_status_data.vcp, vcp_string(ar2.meta_rda_status_data.vcp)), (canvas.width() - 50) as f64, 50.0 + (state.prefs.fcs as f64))?;
|
||||||
let recorded = parse_date(ar2.volume_header_record.date, ar2.volume_header_record.time);
|
let recorded = parse_date(ar2.volume_header_record.date, ar2.volume_header_record.time);
|
||||||
let delay = time - recorded;
|
let delay = time - recorded;
|
||||||
ctx.fill_text(&format!("SITE DELAY {}:{}:{}", delay.num_hours(), delay.num_minutes() - (delay.num_hours() * 60), delay.num_seconds() - (delay.num_minutes() - (delay.num_hours() * 60) * 60)), (canvas.width() - 50) as f64, 50.0 + (state.prefs.fcs as f64 * 2.0))?;
|
|
||||||
|
let seconds = delay.num_seconds() % 60;
|
||||||
|
let minutes = (delay.num_seconds() / 60) % 60;
|
||||||
|
let hours = (delay.num_seconds() / 60) / 60;
|
||||||
|
|
||||||
|
ctx.fill_text(&format!("SITE DELAY {:0>2}:{:0>2}:{:0>2}", hours, minutes, seconds), (canvas.width() - 50) as f64, 50.0 + (state.prefs.fcs as f64 * 2.0))?;
|
||||||
} else {
|
} else {
|
||||||
ctx.fill_text("SITE INFORMATION UNAVAILABLE", (canvas.width() - 50) as f64, 50.0 + state.prefs.fcs as f64)?;
|
ctx.fill_text("SITE INFORMATION UNAVAILABLE", (canvas.width() - 50) as f64, 50.0 + state.prefs.fcs as f64)?;
|
||||||
ctx.fill_text("DELAY INFORMATION UNAVAILABLE", (canvas.width() - 50) as f64, 50.0 + (state.prefs.fcs * 2) as f64)?;
|
ctx.fill_text("DELAY INFORMATION UNAVAILABLE", (canvas.width() - 50) as f64, 50.0 + (state.prefs.fcs * 2) as f64)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// mode info
|
||||||
|
ctx.fill_text("MODE", (canvas.width() - 50) as f64, (canvas.height() / 3) as f64)?;
|
||||||
|
let modelines = [
|
||||||
|
[Reflectivity, Velocity, SpectrumWidth].as_slice(),
|
||||||
|
[DifferentialReflectivity, DifferentialPhase, CorrelationCoefficient].as_slice(),
|
||||||
|
[ClutterFilterPowerRemoved, RadarInoperative].as_slice()
|
||||||
|
];
|
||||||
|
|
||||||
|
let mut available_modes = vec![];
|
||||||
|
|
||||||
|
if let Some(ar2) = &state.ar2 {
|
||||||
|
let types = ar2.elevations.get(&state.selected_elevation).unwrap();
|
||||||
|
available_modes = types[0].available_data.keys().collect();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (line_no, line) in modelines.iter().enumerate() {
|
||||||
|
let x = (canvas.width() - 50) as f64;
|
||||||
|
let y = (canvas.height() / 3) as f64 + (state.prefs.fcs * (line_no + 1)) as f64;
|
||||||
|
for (item_no, item) in line.iter().enumerate() {
|
||||||
|
|
||||||
|
let pad_start = item_no;
|
||||||
|
let pad_end = 2 - item_no;
|
||||||
|
let pad_start = " ".repeat(pad_start * 6);
|
||||||
|
let pad_end = " ".repeat(pad_end * 6);
|
||||||
|
let middle = if state.scope_mode == *item { item.selected() } else { item.unselected() };
|
||||||
|
|
||||||
|
if state.scope_mode == *item {
|
||||||
|
// display selected color
|
||||||
|
if *item == RadarInoperative {
|
||||||
|
ctx.set_fill_style(&JsValue::from_str(TEXT_COLOR_RED));
|
||||||
|
ctx.fill_text(">RADR INOP<", x, y)?;
|
||||||
|
ctx.set_fill_style(&JsValue::from_str(TEXT_COLOR_GREEN));
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
ctx.set_fill_style(&JsValue::from_str(TEXT_COLOR_WHITE));
|
||||||
|
}
|
||||||
|
} else if *item == RadarInoperative {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if available_modes.contains(&&item.rname().to_string()) {
|
||||||
|
ctx.fill_text(&format!(" {}{}{}", pad_start, middle, pad_end), x, y)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if state.scope_mode == *item {
|
||||||
|
// reset
|
||||||
|
ctx.set_fill_style(&JsValue::from_str(TEXT_COLOR_GREEN));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.fill_text("ELEVATIONS", (canvas.width() - 50) as f64, ((canvas.height() / 7) * 4) as f64)?;
|
||||||
|
if let Some(ar2) = &state.ar2 {
|
||||||
|
for (line_no, (elevation_no, scans)) in ar2.elevations.iter().sorted_by(|a, b| {
|
||||||
|
Ord::cmp(&a.0, &b.0)
|
||||||
|
}).enumerate() {
|
||||||
|
if state.selected_elevation != *elevation_no {
|
||||||
|
ctx.fill_text(&format!(" {:0>3} ", elevation_no), (canvas.width() - 50) as f64, ((canvas.height() / 7) * 4) as f64 + (state.prefs.fcs * (line_no + 1)) as f64)?;
|
||||||
|
} else {
|
||||||
|
ctx.set_fill_style(&JsValue::from_str(TEXT_COLOR_WHITE));
|
||||||
|
ctx.fill_text(&format!(">{:0>3}<", elevation_no), (canvas.width() - 50) as f64, ((canvas.height() / 7) * 4) as f64 + (state.prefs.fcs * (line_no + 1)) as f64)?;
|
||||||
|
ctx.set_fill_style(&JsValue::from_str(TEXT_COLOR_GREEN));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ctx.fill_text("ELEVATION INFORMATION UNAVAILABLE", (canvas.width() - 50) as f64, ((canvas.height() / 7) * 4) as f64 + state.prefs.fcs as f64)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ACTUAL DATA RENDERING
|
||||||
|
if let Some(ar2) = &state.ar2 {
|
||||||
|
let px_per_km = canvas.height() / RANGE as u32;
|
||||||
|
let xc = canvas.width() / 2;
|
||||||
|
let yc = canvas.height() / 2;
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
|
@ -12,7 +12,8 @@ pub struct ScopeState {
|
||||||
pub prefs: Preferences,
|
pub prefs: Preferences,
|
||||||
pub command_buf: String,
|
pub command_buf: String,
|
||||||
pub command_buf_response_mode: bool,
|
pub command_buf_response_mode: bool,
|
||||||
pub new_data_available: bool
|
pub new_data_available: bool,
|
||||||
|
pub selected_elevation: usize
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct RenderState {
|
pub struct RenderState {
|
||||||
|
|
|
@ -23,7 +23,8 @@ pub mod message31;
|
||||||
pub struct Nexrad2Chunk {
|
pub struct Nexrad2Chunk {
|
||||||
pub volume_header_record: VolumeHeaderRecord,
|
pub volume_header_record: VolumeHeaderRecord,
|
||||||
pub chunks: Vec<Vec<Message>>,
|
pub chunks: Vec<Vec<Message>>,
|
||||||
pub meta_rda_status_data: Msg02RDAStatusData
|
pub meta_rda_status_data: Msg02RDAStatusData,
|
||||||
|
pub elevations: HashMap<usize, Vec<Message31>>
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
@ -130,6 +131,7 @@ pub fn parse_nx2_chunk(cursor: &mut (impl Read + Seek)) -> Result<Nexrad2Chunk,
|
||||||
trace!("Loaded - {:#?}", header);
|
trace!("Loaded - {:#?}", header);
|
||||||
|
|
||||||
let mut records: Vec<Vec<Message>> = vec![];
|
let mut records: Vec<Vec<Message>> = vec![];
|
||||||
|
let mut elevations: HashMap<usize, Vec<Message31>> = HashMap::new();
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
// LDM records
|
// LDM records
|
||||||
|
@ -344,17 +346,26 @@ pub fn parse_nx2_chunk(cursor: &mut (impl Read + Seek)) -> Result<Nexrad2Chunk,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let message = Message::Msg31(Message31 {
|
let message31 = Message31 {
|
||||||
header: header_struct,
|
header: header_struct,
|
||||||
volume_info: volume_data,
|
volume_info: volume_data,
|
||||||
elevation_info: elevation_data,
|
elevation_info: elevation_data,
|
||||||
radial_info: radial_data,
|
radial_info: radial_data,
|
||||||
available_data: data_map
|
available_data: data_map
|
||||||
});
|
};
|
||||||
|
|
||||||
|
if let Some(elev_mut) = elevations.get_mut(&(message31.header.elevation_number as usize)) {
|
||||||
|
elev_mut.push(message31.clone());
|
||||||
|
} else {
|
||||||
|
elevations.insert(message31.header.elevation_number as usize, vec![message31.clone()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
let message = Message::Msg31(message31);
|
||||||
|
|
||||||
trace!("{:#?}", message);
|
trace!("{:#?}", message);
|
||||||
|
|
||||||
messages.push(message);
|
messages.push(message);
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -384,8 +395,6 @@ pub fn parse_nx2_chunk(cursor: &mut (impl Read + Seek)) -> Result<Nexrad2Chunk,
|
||||||
trace!("{} messages loaded from chunk", messages.len());
|
trace!("{} messages loaded from chunk", messages.len());
|
||||||
|
|
||||||
records.push(messages);
|
records.push(messages);
|
||||||
|
|
||||||
// break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
info!("Extracting meta records");
|
info!("Extracting meta records");
|
||||||
|
@ -414,7 +423,8 @@ pub fn parse_nx2_chunk(cursor: &mut (impl Read + Seek)) -> Result<Nexrad2Chunk,
|
||||||
Ok(Nexrad2Chunk {
|
Ok(Nexrad2Chunk {
|
||||||
volume_header_record: header,
|
volume_header_record: header,
|
||||||
chunks: records,
|
chunks: records,
|
||||||
meta_rda_status_data: msg2
|
meta_rda_status_data: msg2,
|
||||||
|
elevations
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::fmt::{Debug, Formatter};
|
use std::fmt::{Debug, Formatter};
|
||||||
|
use std::io::{Cursor, Read};
|
||||||
|
|
||||||
pub const MSG31_HEADER_LENGTH: usize = 4 + 4 + 2 + 2 + 4 + 1 + 1 + 2 + 1 + 1 + 1 + 1 + 4 + 1 + 1 + 2;
|
pub const MSG31_HEADER_LENGTH: usize = 4 + 4 + 2 + 2 + 4 + 1 + 1 + 2 + 1 + 1 + 1 + 1 + 4 + 1 + 1 + 2;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
#[cfg_attr(feature = "serde_derive", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde_derive", derive(serde::Serialize, serde::Deserialize))]
|
||||||
pub struct Message31Header {
|
pub struct Message31Header {
|
||||||
pub radar_identifier: String,
|
pub radar_identifier: String,
|
||||||
|
@ -26,7 +27,7 @@ pub struct Message31Header {
|
||||||
|
|
||||||
pub const VOLUME_DATA_LENGTH: usize = 1 + 3 + 2 + 1 + 1 + 4 + 4 + 2 + 2 + 4 + 4 + 4 + 4 + 4 + 2 + 2;
|
pub const VOLUME_DATA_LENGTH: usize = 1 + 3 + 2 + 1 + 1 + 4 + 4 + 2 + 2 + 4 + 4 + 4 + 4 + 4 + 2 + 2;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
#[cfg_attr(feature = "serde_derive", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde_derive", derive(serde::Serialize, serde::Deserialize))]
|
||||||
pub struct VolumeData {
|
pub struct VolumeData {
|
||||||
pub datablock_type: String,
|
pub datablock_type: String,
|
||||||
|
@ -49,7 +50,7 @@ pub struct VolumeData {
|
||||||
|
|
||||||
pub const ELEVATION_DATA_LENGTH: usize = 1 + 3 + 2 + 2 + 4;
|
pub const ELEVATION_DATA_LENGTH: usize = 1 + 3 + 2 + 2 + 4;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
#[cfg_attr(feature = "serde_derive", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde_derive", derive(serde::Serialize, serde::Deserialize))]
|
||||||
pub struct ElevationData {
|
pub struct ElevationData {
|
||||||
pub datablock_type: String,
|
pub datablock_type: String,
|
||||||
|
@ -61,7 +62,7 @@ pub struct ElevationData {
|
||||||
|
|
||||||
pub const RADIAL_DATA_LENGTH: usize = 1 + 3 + 2 + 2 + 4 + 4 + 2 + 2 + 4 + 4;
|
pub const RADIAL_DATA_LENGTH: usize = 1 + 3 + 2 + 2 + 4 + 4 + 2 + 2 + 4 + 4;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
#[cfg_attr(feature = "serde_derive", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde_derive", derive(serde::Serialize, serde::Deserialize))]
|
||||||
pub struct RadialData {
|
pub struct RadialData {
|
||||||
pub datablock_type: String,
|
pub datablock_type: String,
|
||||||
|
@ -78,7 +79,7 @@ pub struct RadialData {
|
||||||
|
|
||||||
pub const GENERIC_DATA_MOMENT_LEN: usize = 1 + 3 + 4 + 2 + 2 + 2 + 2 + 2 + 1 + 1 + 4 + 4;
|
pub const GENERIC_DATA_MOMENT_LEN: usize = 1 + 3 + 4 + 2 + 2 + 2 + 2 + 2 + 1 + 1 + 4 + 4;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
#[cfg_attr(feature = "serde_derive", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde_derive", derive(serde::Serialize, serde::Deserialize))]
|
||||||
pub struct GenericDataMoment {
|
pub struct GenericDataMoment {
|
||||||
pub datablock_type: String,
|
pub datablock_type: String,
|
||||||
|
@ -95,6 +96,7 @@ pub struct GenericDataMoment {
|
||||||
pub offset: f32
|
pub offset: f32
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Clone)]
|
||||||
#[cfg_attr(feature = "serde_derive", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde_derive", derive(serde::Serialize, serde::Deserialize))]
|
||||||
pub struct DataMoment {
|
pub struct DataMoment {
|
||||||
pub gdm: GenericDataMoment,
|
pub gdm: GenericDataMoment,
|
||||||
|
@ -107,7 +109,7 @@ impl Debug for DataMoment {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
#[cfg_attr(feature = "serde_derive", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde_derive", derive(serde::Serialize, serde::Deserialize))]
|
||||||
pub struct Message31 {
|
pub struct Message31 {
|
||||||
pub header: Message31Header,
|
pub header: Message31Header,
|
||||||
|
@ -116,3 +118,43 @@ pub struct Message31 {
|
||||||
pub radial_info: Option<RadialData>,
|
pub radial_info: Option<RadialData>,
|
||||||
pub available_data: HashMap<String, DataMoment>
|
pub available_data: HashMap<String, DataMoment>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl DataMoment {
|
||||||
|
pub fn scaled_data(&self) -> Vec<f32> {
|
||||||
|
let mut gates = vec![0u16; self.gdm.data_moment_gate_count as usize];
|
||||||
|
|
||||||
|
if self.gdm.data_word_size == 8 {
|
||||||
|
for byte in &self.data {
|
||||||
|
gates.push(*byte as u16);
|
||||||
|
}
|
||||||
|
} else if self.gdm.data_word_size == 16 {
|
||||||
|
for chunk in self.data.chunks(2) {
|
||||||
|
gates.push(u16::from_be_bytes(chunk.try_into().unwrap()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut scaled_data = vec![];
|
||||||
|
|
||||||
|
for gate in &gates {
|
||||||
|
if *gate == 0 {
|
||||||
|
// below threshold
|
||||||
|
scaled_data.push(999.0);
|
||||||
|
} else if *gate == 1 {
|
||||||
|
// folded
|
||||||
|
scaled_data.push(998.0);
|
||||||
|
} else {
|
||||||
|
scaled_data.push(scale_uint(*gate, self.gdm.offset, self.gdm.scale))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
scaled_data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn scale_uint(gate: u16, offset: f32, scale: f32) -> f32 {
|
||||||
|
if scale == 0.0 {
|
||||||
|
gate as f32
|
||||||
|
} else {
|
||||||
|
(gate as f32 - offset) / scale
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue