From 313dca0483bfe4d0de8bce004921625692e63af1 Mon Sep 17 00:00:00 2001 From: core Date: Mon, 6 Nov 2023 21:30:52 -0500 Subject: [PATCH] looks like i am going to rewrite the entire rendering engine again --- nexrad-browser/src/colors.rs | 206 +++++++++++++++++++++++++++ nexrad-browser/src/command.rs | 2 + nexrad-browser/src/lib.rs | 4 +- nexrad-browser/src/mode.rs | 2 +- nexrad-browser/src/rendering.rs | 238 +++++++++++++++++++++----------- nexrad-browser/src/scope.rs | 3 +- nexrad-browser/src/utils.rs | 6 +- nexrad2/src/message31.rs | 7 +- 8 files changed, 378 insertions(+), 90 deletions(-) create mode 100644 nexrad-browser/src/colors.rs diff --git a/nexrad-browser/src/colors.rs b/nexrad-browser/src/colors.rs new file mode 100644 index 0000000..eb971a8 --- /dev/null +++ b/nexrad-browser/src/colors.rs @@ -0,0 +1,206 @@ +use nexrad2::message31::MOMENT_DATA_FOLDED; +use crate::mode::Mode; + +pub fn correlation_coefficient(val: f32) -> &'static str { + let gradient = [ + (0.275, "black"), + (0.35, "darkgrey"), + (0.4, "gray"), + (0.5, "silver"), + (0.6, "midnightblue"), + (0.7, "darkblue"), + (0.8, "blue"), + (0.91, "green"), + (0.92, "yellowgreen"), + (0.93, "olivedrab"), + (0.94, "yellow"), + (0.95, "gold"), + (0.96, "orange"), + (0.97, "orangered"), + (0.98, "red"), + (0.99, "firebrick"), + (1.0, "maroon"), + (1.01, "darkmagenta"), + (1.02, "purple"), + (1.03, "mediumvioletred"), + (1.045, "pink"), + (1.05, "lavenderblush") + ]; + + for (threshold, color) in gradient { + if val < threshold { + return color; + } + } + + "white" +} + +pub fn spectrum_width(val: f32) -> &'static str { + let gradient = [ + (1.0, "black"), + (2.0, "#222222"), + (3.0, "#333333"), + (4.0, "#444444"), + (5.0, "#555555"), + (6.0, "#666666"), + (7.0, "#777777"), + (8.0, "#888888"), + (9.0, "#999999"), + (10.0, "burlywood"), + (11.0, "sandybrown"), + (12.0, "gold"), + (13.0, "orange"), + (15.0, "orangered"), + (17.0, "red"), + (19.0, "firebrick"), + (23.0, "darkred"), + (25.0, "hotpink"), + (27.0, "fuchsia"), + (30.0, "lavender"), + (32.0, "white"), + (35.0, "yellow"), + (60.0, "lime"), + (1000.0, "purple") + ]; + + for (threshold, color) in gradient { + if val < threshold { + return color; + } + } + + "white" +} + +pub fn differential_reflectivity(val: f32) -> &'static str { + let gradient = [ + (-3.0, "black"), + (-1.0, "#333333"), + (-0.5, "#666666"), + (0.1, "#999999"), + (0.0, "#cccccc"), + (0.1, "white"), + (0.25, "navy"), + (0.5, "blue"), + (0.75, "deepskyblue"), + (1.0, "cyan"), + (1.25, "mediumaquamarine"), + (1.5, "lime"), + (1.75, "yellowgreen"), + (2.0, "yellow"), + (2.5, "gold"), + (3.0, "orange"), + (4.0, "orangered"), + (5.0, "red"), + (6.0, "maroon"), + (7.0, "hotpink"), + (10.0, "pink"), + (999.0, "white") + ]; + + for (threshold, color) in gradient { + if val < threshold { + return color; + } + } + + "white" +} + +pub fn dbz_noaa(dbz: f32) -> &'static str { + if dbz < 5.0 || dbz == MOMENT_DATA_FOLDED { + "#000000" + } else if dbz >= 5.0 && dbz < 10.0 { + "#40e8e3" + } else if dbz >= 10.0 && dbz < 15.0 { + "#26a4fa" + } else if dbz >= 15.0 && dbz < 20.0 { + "#0030ed" + } else if dbz >= 20.0 && dbz < 25.0 { + "#49fb3e" + } else if dbz >= 25.0 && dbz < 30.0 { + "#36c22e" + } else if dbz >= 30.0 && dbz < 35.0 { + "#278c1e" + } else if dbz >= 35.0 && dbz < 40.0 { + "#fef543" + } else if dbz >= 40.0 && dbz < 45.0 { + "#ebb433" + } else if dbz >= 45.0 && dbz < 50.0 { + "#f6952e" + } else if dbz >= 50.0 && dbz < 55.0 { + "#f80a26" + } else if dbz >= 55.0 && dbz < 60.0 { + "#cb0516" + } else if dbz >= 60.0 && dbz < 65.0 { + "#a90813" + } else if dbz >= 65.0 && dbz < 70.0 { + "#ee34fa" + } else if dbz >= 79.0 && dbz < 75.0 { + "#9161c4" + } else { + "#ffffff" + } +} + +pub fn velocity(vel: f32) -> &'static str { + if vel == MOMENT_DATA_FOLDED { + return "#691ac1"; + } + + let colors = [ + "#F91473", // 140 + "#AA1079", // 130 + "#6E0E80", // 120 + "#2E0E84", // 110 + "#151F93", // 100 + "#236FB3", // 90 + "#41DADB", // 80 + "#66E1E2", // 70 + "#9EE8EA", // 60 + "#57FA63", // 50 + "#31E32B", // 40 + // "#21BE0A", // 35 + "#24AA1F", // 30 + "#197613", // 20 + "#456742", // -10 + "#634F50", // 0 + "#6e2e39", // 10 + "#7F030C", // 20 + "#B60716", // 30 + // "#C5000D", // 35 + "#F32245", // 40 + "#F6508A", // 50 + "#FB8BBF", // 60 + "#FDDE93", // 70 + "#FCB470", // 80 + "#FA814B", // 90 + "#DD603C", // 100 + "#B7452D", // 110 + "#932C20", // 120 + "#711614", // 130 + "#520106", // 140 + ]; + + let i = scale_int((vel.floor()) as i32, 140, -140, (colors.len() - 1) as i32, 0); + + colors[i as usize] +} + +pub fn scale_int(val: i32, o_max: i32, o_min: i32, n_max: i32, n_min: i32) -> i32 { + (((val - o_min) * n_max - n_min) / o_max - o_min) + n_min +} + +pub fn color_scheme(product: Mode, value: f32) -> &'static str { + match product { + Mode::Reflectivity => dbz_noaa(value), + Mode::Velocity => velocity(value), + Mode::SpectrumWidth => spectrum_width(value), + Mode::DifferentialReflectivity => differential_reflectivity(value), + Mode::DifferentialPhase => correlation_coefficient(value), + Mode::CorrelationCoefficient => correlation_coefficient(value), + Mode::ClutterFilterPowerRemoved => dbz_noaa(value), + Mode::RadarInoperative => "#ff0000" + } +} \ No newline at end of file diff --git a/nexrad-browser/src/command.rs b/nexrad-browser/src/command.rs index 7d26bb2..3c55c1f 100644 --- a/nexrad-browser/src/command.rs +++ b/nexrad-browser/src/command.rs @@ -79,6 +79,8 @@ pub fn exec(state: &mut ScopeState, command: String) { return } } + } else if command == "`T" { + state.show_ui = !state.show_ui; } else { state.command_buf = "UNRECOGNIZED COMMAND".to_string(); state.command_buf_response_mode = true; diff --git a/nexrad-browser/src/lib.rs b/nexrad-browser/src/lib.rs index 895b5ee..75fec4a 100644 --- a/nexrad-browser/src/lib.rs +++ b/nexrad-browser/src/lib.rs @@ -6,6 +6,7 @@ pub mod equirectangular; pub mod sites; pub mod command; pub mod vcp; +pub mod colors; use std::io::Cursor; use std::ops::{Deref, DerefMut}; @@ -63,7 +64,8 @@ pub fn __nxrd_browser_init() -> AbiScopeState { command_buf: String::new(), command_buf_response_mode: false, new_data_available: false, - selected_elevation: 1 + selected_elevation: 1, + show_ui: false }; info!("nexrad-browser initialized successfully"); diff --git a/nexrad-browser/src/mode.rs b/nexrad-browser/src/mode.rs index 3f77059..a6f245e 100644 --- a/nexrad-browser/src/mode.rs +++ b/nexrad-browser/src/mode.rs @@ -1,4 +1,4 @@ -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, PartialEq, Eq, Copy, Clone)] pub enum Mode { /// REF Reflectivity, diff --git a/nexrad-browser/src/rendering.rs b/nexrad-browser/src/rendering.rs index af3c95c..08c6147 100644 --- a/nexrad-browser/src/rendering.rs +++ b/nexrad-browser/src/rendering.rs @@ -1,7 +1,10 @@ +use std::f64::consts::PI; use chrono::{DateTime, Timelike, Utc}; use itertools::Itertools; -use log::info; +use log::{debug, info}; use wasm_bindgen::JsValue; +use nexrad2::message31::MOMENT_DATA_BELOW_THRESHOLD; +use crate::colors::color_scheme; use crate::mode::Mode; use crate::mode::Mode::{ClutterFilterPowerRemoved, CorrelationCoefficient, DifferentialPhase, DifferentialReflectivity, RadarInoperative, Reflectivity, SpectrumWidth, Velocity}; use crate::rescaleCanvas; @@ -48,19 +51,20 @@ pub fn render(state: &mut ScopeState) -> Result<(), JsValue> { ctx.set_fill_style(&JsValue::from_str(TEXT_COLOR_GREEN)); // render out the scope id line - ctx.fill_text(&format!("NEXRAD {} {}", state.ar2.as_ref().map(|u| u.volume_header_record.icao.as_str()).unwrap_or("INOP"), zulu(time)), 50.0, 50.0)?; - - if state.ar2.is_none() { - // inop alert - ctx.set_fill_style(&JsValue::from_str(TEXT_COLOR_RED)); - ctx.fill_text("RADAR INOPERATIVE NO DATA LOADED", 50.0, 50.0 + (state.prefs.fcs as f64))?; - ctx.set_fill_style(&JsValue::from_str(TEXT_COLOR_GREEN)); - } - if state.new_data_available { - // inop alert - ctx.set_fill_style(&JsValue::from_str(TEXT_COLOR_RED)); - ctx.fill_text("NEW DATA AVAIL RLD RQD", 50.0, 50.0 + (state.prefs.fcs as f64) * 2.0)?; - ctx.set_fill_style(&JsValue::from_str(TEXT_COLOR_GREEN)); + if state.show_ui { + ctx.fill_text(&format!("NEXRAD {} {}", state.ar2.as_ref().map(|u| u.volume_header_record.icao.as_str()).unwrap_or("INOP"), zulu(time)), 50.0, 50.0)?; + if state.ar2.is_none() { + // inop alert + ctx.set_fill_style(&JsValue::from_str(TEXT_COLOR_RED)); + ctx.fill_text("RADAR INOPERATIVE NO DATA LOADED", 50.0, 50.0 + (state.prefs.fcs as f64))?; + ctx.set_fill_style(&JsValue::from_str(TEXT_COLOR_GREEN)); + } + if state.new_data_available { + // inop alert + ctx.set_fill_style(&JsValue::from_str(TEXT_COLOR_RED)); + ctx.fill_text("NEW DATA AVAIL RLD RQD", 50.0, 50.0 + (state.prefs.fcs as f64) * 2.0)?; + ctx.set_fill_style(&JsValue::from_str(TEXT_COLOR_GREEN)); + } } // render the command buffer @@ -69,97 +73,167 @@ pub fn render(state: &mut ScopeState) -> Result<(), JsValue> { } // render the radar site info - ctx.set_text_align("right"); - ctx.fill_text("RADAR SITE", (canvas.width() - 50) as f64, 50.0)?; - if let Some(ar2) = &state.ar2 { - 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 delay = time - recorded; + if state.show_ui { + ctx.set_text_align("right"); + ctx.fill_text("RADAR SITE", (canvas.width() - 50) as f64, 50.0)?; + if let Some(ar2) = &state.ar2 { + 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 delay = time - recorded; - let seconds = delay.num_seconds() % 60; - let minutes = (delay.num_seconds() / 60) % 60; - let hours = (delay.num_seconds() / 60) / 60; + let seconds = delay.num_seconds() % 60; + let minutes = (delay.num_seconds() / 60) % 60; + let hours = delay.num_seconds() / 3600; - 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 { - 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(&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 { + 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)?; + } - // 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() - ]; + // 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![]; + 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(); - } + 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() { + 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() }; - 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)); + 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; - } 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 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)); + 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)); + ctx.fill_text("ELEVATIONS", (canvas.width() - 50) as f64, ((canvas.height() / 7) * 4) as f64)?; + if let Some(ar2) = &state.ar2 { + let mut line_no = 0; + + for chunk in &ar2.elevations.iter().sorted_by(|a, b| { + Ord::cmp(&a.0, &b.0) + }).enumerate().chunks(4) { + for (no_in_line, (elevation_no, scans)) in chunk { + if state.selected_elevation != *elevation_no { + ctx.fill_text(&format!(" {:0>3} {}", elevation_no, " ".repeat(5 * (3 - (no_in_line % 4)))), (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, " ".repeat(5 * (3 - (no_in_line % 4)))), (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)); + } + } + line_no += 1; } + } else { + ctx.fill_text("ELEVATION INFORMATION UNAVAILABLE", (canvas.width() - 50) as f64, ((canvas.height() / 7) * 4) as f64 + state.prefs.fcs as f64)?; } - } 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 px_per_km = (canvas.height() / RANGE as u32) as f32; let xc = canvas.width() / 2; let yc = canvas.height() / 2; + + let radials = ar2.elevations.get(&state.selected_elevation).unwrap(); + + let first_gate_px = (radials[0].available_data.get("REF").expect("reflectivity is missing!").gdm.data_moment_range) as f32 / 1000.0 * px_per_km; + let gate_interval_km = (radials[0].available_data.get("REF").expect("reflectivity is missing!").gdm.data_moment_range_sample_interval) as f32 / 1000.0; + let gate_width_px = gate_interval_km * px_per_km; + + for radial in radials { + /* weird rounding stolen from go-nexrad */ + let mut azimuth_angle = (radial.header.azimuth_angle as f64) - 90.0; + if azimuth_angle < 0.0 { + azimuth_angle = 360.0 + azimuth_angle; + } + + let mut azimuth = azimuth_angle.floor(); + + let azimuth_spacing = match radial.header.azimuth_resolution_spacing { + 1 => 0.5, + _ => 1.0 + }; + if (azimuth_angle + azimuth_spacing).floor() > azimuth { + azimuth += azimuth_spacing; + } + /* conclude the weird rounding stolen from go-nexrad */ + + // Angles specified clockwise in radians + let start_angle = azimuth * (PI / 180.0); + let end_angle = azimuth_spacing * (PI / 180.0); + + let mut distance_x = first_gate_px; + let mut distance_y = first_gate_px; + + // line width + // line cap + + let gates = radial.available_data.get(state.scope_mode.rname()).expect("selected unavailable product").scaled_data(); + + let num_gates = gates.len(); + + for (num, value) in gates.iter().enumerate() { + if *value != MOMENT_DATA_BELOW_THRESHOLD { + + ctx.move_to(xc as f64 + start_angle.cos() * distance_x as f64, yc as f64 + start_angle.sin() * distance_y as f64); + + ctx.begin_path(); + + if num == 0 { + ctx.ellipse(xc as f64, yc as f64, distance_x as f64, distance_y as f64, 0.0, start_angle - 0.001, end_angle + 0.001)?; + } else if num == num_gates - 1 { + ctx.ellipse(xc as f64, yc as f64, distance_x as f64, distance_y as f64, 0.0, start_angle, end_angle)?; + } else { + ctx.ellipse(xc as f64, yc as f64, distance_x as f64, distance_y as f64, 0.0, start_angle, end_angle + 0.001)?; + } + + ctx.set_stroke_style(&JsValue::from_str(&format!("{}px {}", gate_width_px + 1.0, color_scheme(state.scope_mode, *value)))); + ctx.stroke(); + } + + distance_x += gate_width_px; + distance_y += gate_width_px; + + azimuth += azimuth_spacing; + } + } } Ok(()) diff --git a/nexrad-browser/src/scope.rs b/nexrad-browser/src/scope.rs index ec6ba94..bc95670 100644 --- a/nexrad-browser/src/scope.rs +++ b/nexrad-browser/src/scope.rs @@ -13,7 +13,8 @@ pub struct ScopeState { pub command_buf: String, pub command_buf_response_mode: bool, pub new_data_available: bool, - pub selected_elevation: usize + pub selected_elevation: usize, + pub show_ui: bool } pub struct RenderState { diff --git a/nexrad-browser/src/utils.rs b/nexrad-browser/src/utils.rs index f67c61b..e45c9e1 100644 --- a/nexrad-browser/src/utils.rs +++ b/nexrad-browser/src/utils.rs @@ -14,6 +14,8 @@ pub fn set_panic_hook() { pub const MILLIS_PER_DAY: u128 = 86_400_000; pub fn parse_date(julian: u32, millis: u32) -> DateTime { - let unix_time_millis = (julian as u128) * MILLIS_PER_DAY + millis as u128; - DateTime::UNIX_EPOCH + Duration::milliseconds(unix_time_millis as i64) + let mut date = DateTime::UNIX_EPOCH; + date += Duration::days(julian as i64 - 1); + date += Duration::milliseconds(millis as i64); + date } \ No newline at end of file diff --git a/nexrad2/src/message31.rs b/nexrad2/src/message31.rs index 7d93632..6e4cb08 100644 --- a/nexrad2/src/message31.rs +++ b/nexrad2/src/message31.rs @@ -3,7 +3,8 @@ 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 MOMENT_DATA_BELOW_THRESHOLD: f32 = 999.0; +pub const MOMENT_DATA_FOLDED: f32 = 998.0; #[derive(Debug, Clone, PartialEq)] #[cfg_attr(feature = "serde_derive", derive(serde::Serialize, serde::Deserialize))] pub struct Message31Header { @@ -138,10 +139,10 @@ impl DataMoment { for gate in &gates { if *gate == 0 { // below threshold - scaled_data.push(999.0); + scaled_data.push(MOMENT_DATA_BELOW_THRESHOLD); } else if *gate == 1 { // folded - scaled_data.push(998.0); + scaled_data.push(MOMENT_DATA_FOLDED); } else { scaled_data.push(scale_uint(*gate, self.gdm.offset, self.gdm.scale)) }