looks like i am going to rewrite the entire rendering engine again

This commit is contained in:
core 2023-11-06 21:30:52 -05:00
parent 4b7ec8c058
commit 313dca0483
Signed by: core
GPG Key ID: FDBF740DADDCEECF
8 changed files with 378 additions and 90 deletions

View File

@ -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"
}
}

View File

@ -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;

View File

@ -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");

View File

@ -1,4 +1,4 @@
#[derive(Debug, PartialEq, Eq)]
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub enum Mode {
/// REF
Reflectivity,

View File

@ -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,8 +51,8 @@ pub fn render(state: &mut ScopeState) -> Result<(), JsValue> {
ctx.set_fill_style(&JsValue::from_str(TEXT_COLOR_GREEN));
// render out the scope id line
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));
@ -62,6 +65,7 @@ pub fn render(state: &mut ScopeState) -> Result<(), JsValue> {
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
for (line_no, line) in state.command_buf.split('\n').enumerate() {
@ -69,6 +73,7 @@ pub fn render(state: &mut ScopeState) -> Result<(), JsValue> {
}
// render the radar site info
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 {
@ -78,7 +83,7 @@ pub fn render(state: &mut ScopeState) -> Result<(), JsValue> {
let seconds = delay.num_seconds() % 60;
let minutes = (delay.num_seconds() / 60) % 60;
let hours = (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 {
@ -105,7 +110,6 @@ pub fn render(state: &mut ScopeState) -> Result<(), JsValue> {
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);
@ -140,26 +144,96 @@ pub fn render(state: &mut ScopeState) -> Result<(), JsValue> {
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| {
let mut line_no = 0;
for chunk in &ar2.elevations.iter().sorted_by(|a, b| {
Ord::cmp(&a.0, &b.0)
}).enumerate() {
}).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), (canvas.width() - 50) as f64, ((canvas.height() / 7) * 4) as f64 + (state.prefs.fcs * (line_no + 1)) as f64)?;
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), (canvas.width() - 50) as f64, ((canvas.height() / 7) * 4) as f64 + (state.prefs.fcs * (line_no + 1)) as f64)?;
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)?;
}
}
// 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(())

View File

@ -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 {

View File

@ -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<Utc> {
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
}

View File

@ -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))
}