work
This commit is contained in:
parent
ad0f0d444c
commit
a2153d8219
|
@ -7,6 +7,7 @@
|
|||
<sourceFolder url="file://$MODULE_DIR$/nxar2/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/rtwx/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/rtwx-desktop/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/rtwx-render/src" isTestSource="false" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/target" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
|
|
|
@ -4,7 +4,8 @@ members = [
|
|||
"rtwx",
|
||||
"nexrad-browser",
|
||||
"nxar2",
|
||||
"rtwx-desktop"
|
||||
"rtwx-desktop",
|
||||
"rtwx-render"
|
||||
]
|
||||
resolver = "2"
|
||||
|
||||
|
|
Binary file not shown.
|
@ -4,7 +4,7 @@ use crate::rendering::scope::ScopeState;
|
|||
|
||||
|
||||
pub fn should_newline(state: &mut ScopeState) -> bool {
|
||||
state.command_buf.starts_with("MODE SET") || state.command_buf.starts_with("ELEVATION SET") || state.command_buf.starts_with("FCS SET")
|
||||
state.command_buf.starts_with("MODE SET") || state.command_buf.starts_with("ELEVATION SET") || state.command_buf.starts_with("FCS SET") || state.command_buf.starts_with("RANGE SET")
|
||||
}
|
||||
|
||||
pub fn exec(state: &mut ScopeState, command: String) {
|
||||
|
@ -19,6 +19,10 @@ pub fn exec(state: &mut ScopeState, command: String) {
|
|||
state.command_buf = state.prefs.fcs.to_string();
|
||||
state.command_buf_response_mode = true;
|
||||
return;
|
||||
} else if command == "RANGE" {
|
||||
state.command_buf = state.prefs.range.to_string();
|
||||
state.command_buf_response_mode = true;
|
||||
return;
|
||||
} else if command.starts_with("FCS SET") {
|
||||
if tokens.len() < 3 {
|
||||
state.command_buf = "ARGUMENT INVALID".to_string();
|
||||
|
@ -35,6 +39,23 @@ pub fn exec(state: &mut ScopeState, command: String) {
|
|||
};
|
||||
state.prefs.fcs = new_val;
|
||||
return;
|
||||
} else if command.starts_with("RANGE SET") {
|
||||
if tokens.len() < 3 {
|
||||
state.command_buf = "ARGUMENT INVALID".to_string();
|
||||
state.command_buf_response_mode = true;
|
||||
return;
|
||||
}
|
||||
let new_val: f32 = match tokens[2].parse() {
|
||||
Ok(v) => v,
|
||||
Err(_) => {
|
||||
state.command_buf = "ARGUMENT INVALID".to_string();
|
||||
state.command_buf_response_mode = true;
|
||||
return;
|
||||
}
|
||||
};
|
||||
state.prefs.range = new_val;
|
||||
state.recalculate_scope_display();
|
||||
return;
|
||||
} else if command.starts_with("MODE SET") {
|
||||
if tokens.len() < 3 {
|
||||
state.command_buf = "ARGUMENT INVALID".to_string();
|
||||
|
@ -62,6 +83,8 @@ pub fn exec(state: &mut ScopeState, command: String) {
|
|||
"CFP" => Mode::ClutterFilterPowerRemoved,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
state.recalculate_scope_display();
|
||||
} else {
|
||||
state.command_buf = "CANNOT SET MODE\nRADAR INOPERATIVE".to_string();
|
||||
state.command_buf_response_mode = true;
|
||||
|
@ -90,6 +113,7 @@ pub fn exec(state: &mut ScopeState, command: String) {
|
|||
// reset scope back to reflectivity which is included in every elevation
|
||||
state.scope_mode = Mode::Reflectivity;
|
||||
state.selected_elevation = number;
|
||||
state.recalculate_scope_display();
|
||||
} else {
|
||||
state.command_buf = "CANNOT SET MODE\nRADAR INOPERATIVE".to_string();
|
||||
state.command_buf_response_mode = true;
|
||||
|
|
|
@ -99,6 +99,7 @@ pub async fn __nxrd_browser_init(w: u32, h: u32) -> AbiProxy {
|
|||
scope_state.command_buf_response_mode = true;
|
||||
scope_state.command_buf = "NEW DATA LOADED SUCCESSFULLY".to_string();
|
||||
scope_state.selected_elevation = 1;
|
||||
scope_state.recalculate_scope_display();
|
||||
}
|
||||
},
|
||||
Event::WindowEvent {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use crate::mode::Mode;
|
||||
use nexrad2::message31::MOMENT_DATA_FOLDED;
|
||||
use nexrad2::message31::{MOMENT_DATA_BELOW_THRESHOLD, MOMENT_DATA_FOLDED};
|
||||
|
||||
pub fn correlation_coefficient(val: f32) -> [f32; 3] {
|
||||
let gradient = [
|
||||
|
@ -199,6 +199,9 @@ pub fn scale_int(val: i32, o_max: i32, o_min: i32, n_max: i32, n_min: i32) -> i3
|
|||
}
|
||||
|
||||
pub fn color_scheme(product: Mode, value: f32) -> [f32; 3] {
|
||||
if value == MOMENT_DATA_BELOW_THRESHOLD {
|
||||
return [rgb_to_srgb_float_individual_unscaled(0x69 as f32), rgb_to_srgb_float_individual_unscaled(0x1a as f32), rgb_to_srgb_float_individual_unscaled(0xc1 as f32)];
|
||||
}
|
||||
let rgb = match product {
|
||||
Mode::Reflectivity => dbz_noaa(value),
|
||||
Mode::Velocity => velocity(value),
|
||||
|
@ -207,7 +210,7 @@ pub fn color_scheme(product: Mode, value: f32) -> [f32; 3] {
|
|||
Mode::DifferentialPhase => correlation_coefficient(value),
|
||||
Mode::CorrelationCoefficient => correlation_coefficient(value),
|
||||
Mode::ClutterFilterPowerRemoved => dbz_noaa(value),
|
||||
Mode::RadarInoperative => [255.0, 255.0, 255.0],
|
||||
Mode::RadarInoperative => [0x69 as f32, 0x1a as f32, 0xc1 as f32],
|
||||
};
|
||||
|
||||
[rgb_to_srgb_float_individual_unscaled(rgb[0]), rgb_to_srgb_float_individual_unscaled(rgb[1]), rgb_to_srgb_float_individual_unscaled(rgb[2])]
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use std::f64::consts::PI;
|
||||
use std::f32::consts::PI;
|
||||
use crate::mode::Mode;
|
||||
use nexrad2::Nexrad2Chunk;
|
||||
|
||||
|
@ -6,7 +6,7 @@ use nexrad2::Nexrad2Chunk;
|
|||
use std::iter;
|
||||
use chrono::Utc;
|
||||
use itertools::Itertools;
|
||||
use log::info;
|
||||
use log::{debug, info};
|
||||
use wasm_bindgen::JsCast;
|
||||
|
||||
|
||||
|
@ -58,7 +58,10 @@ pub struct WgpuState {
|
|||
pub index_buffer: wgpu::Buffer,
|
||||
pub pipeline: RenderPipeline,
|
||||
pub staging_belt: StagingBelt,
|
||||
pub glyph_brush: GlyphBrush<()>
|
||||
pub glyph_brush: GlyphBrush<()>,
|
||||
pub verticies: Vec<Vertex>,
|
||||
pub indicies: Vec<u16>,
|
||||
pub buffers_need_reinitialization: bool
|
||||
}
|
||||
|
||||
impl ScopeState {
|
||||
|
@ -190,7 +193,10 @@ impl ScopeState {
|
|||
pipeline: render_pipeline,
|
||||
staging_belt,
|
||||
glyph_brush,
|
||||
index_buffer
|
||||
index_buffer,
|
||||
verticies: vec![],
|
||||
indicies: vec![],
|
||||
buffers_need_reinitialization: false
|
||||
};
|
||||
|
||||
info!("initializing the scope");
|
||||
|
@ -216,7 +222,7 @@ impl ScopeState {
|
|||
file_input: file,
|
||||
lat: 30.48500, // jacksonville
|
||||
long: -81.70200, // jacksonville
|
||||
prefs: Preferences { fcs: 24.0 },
|
||||
prefs: Preferences { fcs: 24.0, range: 50.0 },
|
||||
command_buf: String::new(),
|
||||
command_buf_response_mode: false,
|
||||
new_data_available: false,
|
||||
|
@ -247,6 +253,160 @@ impl ScopeState {
|
|||
|
||||
pub fn update(&mut self) {}
|
||||
|
||||
pub fn recalculate_scope_display(&mut self) {
|
||||
self.wgpu.buffers_need_reinitialization = true;
|
||||
self.wgpu.verticies = vec![];
|
||||
self.wgpu.indicies = vec![];
|
||||
let ar2 = self.ar2.as_ref().unwrap();
|
||||
let px_per_km = 1.0 / (self.prefs.range * 2.0);
|
||||
|
||||
let radials = ar2.elevations.get(&self.selected_elevation).unwrap();
|
||||
|
||||
let first_gate_px = radials[0].available_data.get(self.scope_mode.rname()).unwrap().gdm.data_moment_range as f32 / 1000.0 * px_per_km;
|
||||
let gate_interval_km = radials[0].available_data.get(self.scope_mode.rname()).unwrap().gdm.data_moment_range_sample_interval as f32 / 1000.0;
|
||||
let gate_width_px = gate_interval_km * px_per_km;
|
||||
|
||||
for radial in radials {
|
||||
// radial rounding
|
||||
let mut azimuth_angle = radial.header.azimuth_angle - 90.0;
|
||||
if azimuth_angle < 0.0 {
|
||||
azimuth_angle = 360.0 + azimuth_angle;
|
||||
}
|
||||
let azimuth_spacing = match radial.header.azimuth_resolution_spacing {
|
||||
1 => 0.5,
|
||||
_ => 1.0
|
||||
};
|
||||
let mut azimuth = azimuth_angle.floor();
|
||||
if (azimuth_angle + azimuth_spacing).floor() > azimuth {
|
||||
azimuth += azimuth_spacing;
|
||||
}
|
||||
let start_angle = azimuth * (PI / 180.0);
|
||||
let end_angle = (azimuth + azimuth_spacing) * (PI / 180.0);
|
||||
|
||||
let mut distance = first_gate_px;
|
||||
|
||||
let gates = radial.available_data.get(self.scope_mode.rname()).unwrap().scaled_data();
|
||||
|
||||
for (gate_num, gate) in gates.iter().enumerate() {
|
||||
if *gate != MOMENT_DATA_BELOW_THRESHOLD {
|
||||
let a = [(distance * start_angle.cos()) as f32, (distance * start_angle.sin()) as f32, 0.0];
|
||||
let b = [(distance * end_angle.cos()) as f32, (distance * end_angle.sin()) as f32, 0.0];
|
||||
let c = [(distance + gate_width_px * start_angle.cos()) as f32, (distance + gate_width_px * start_angle.sin()) as f32, 0.0];
|
||||
let d = [(distance + gate_width_px * end_angle.cos()) as f32, (distance + gate_width_px * end_angle.sin()) as f32, 0.0];
|
||||
let vertex_a = Vertex { position: a, color: color_scheme(self.scope_mode, *gate) };
|
||||
let vertex_b = Vertex { position: b, color: color_scheme(self.scope_mode, *gate) };
|
||||
let vertex_c = Vertex { position: c, color: color_scheme(self.scope_mode, *gate) };
|
||||
let vertex_d = Vertex { position: d, color: color_scheme(self.scope_mode, *gate) };
|
||||
|
||||
let vindex_a = self.wgpu.verticies.len();
|
||||
self.wgpu.verticies.push(vertex_a);
|
||||
let vindex_b = self.wgpu.verticies.len();
|
||||
self.wgpu.verticies.push(vertex_b);
|
||||
let vindex_c = self.wgpu.verticies.len();
|
||||
self.wgpu.verticies.push(vertex_c);
|
||||
let vindex_d = self.wgpu.verticies.len();
|
||||
self.wgpu.verticies.push(vertex_d);
|
||||
|
||||
// ABC <-> BCD
|
||||
|
||||
self.wgpu.indicies.extend_from_slice(&[vindex_a as u16, vindex_b as u16, vindex_c as u16]);
|
||||
self.wgpu.indicies.extend_from_slice(&[vindex_b as u16, vindex_c as u16, vindex_d as u16]);
|
||||
|
||||
}
|
||||
distance += gate_width_px;
|
||||
azimuth += azimuth_spacing;
|
||||
}
|
||||
}
|
||||
info!("Tesselated, {}/{}", self.wgpu.verticies.len(), self.wgpu.indicies.len());
|
||||
info!("{:?} {:?} {:?} {:?}", self.wgpu.verticies[0], self.wgpu.verticies[1], self.wgpu.verticies[2], self.wgpu.verticies[3]);
|
||||
}
|
||||
|
||||
/*
|
||||
pub fn recalculate_scope_display(&mut self) {
|
||||
self.wgpu.buffers_need_reinitialization = true;
|
||||
self.wgpu.verticies = vec![];
|
||||
self.wgpu.indicies = vec![];
|
||||
let ar2 = self.ar2.as_ref().unwrap();
|
||||
let px_per_km = 1.0 / (self.prefs.range * 2.0);
|
||||
|
||||
let radials = ar2.elevations.get(&self.selected_elevation).unwrap();
|
||||
|
||||
info!("Tesselating {} radials", radials.len());
|
||||
|
||||
let distance_firstgate = ((radials[0].available_data.get(self.scope_mode.rname()).expect("selected mode is missing!").gdm.data_moment_range as f64) / 1000.0) * px_per_km;
|
||||
let distance_gatespacing = ((radials[0].available_data.get(self.scope_mode.rname()).expect("selected mode is missing!").gdm.data_moment_range_sample_interval as f64) / 1000.0) * px_per_km;
|
||||
|
||||
let mut distance_gate = distance_firstgate;
|
||||
|
||||
for radial in radials {
|
||||
let mut azimuth = radial.header.azimuth_angle as f64;
|
||||
|
||||
let azimuth_spacing = match radial.header.azimuth_resolution_spacing {
|
||||
1 => 0.5,
|
||||
_ => 1.0,
|
||||
};
|
||||
|
||||
// line width
|
||||
// line cap
|
||||
|
||||
let gates = radial
|
||||
.available_data
|
||||
.get(self.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 {
|
||||
let az = azimuth * (PI / 180.0);
|
||||
let bz = (azimuth + azimuth_spacing) * (PI / 180.0);
|
||||
|
||||
let d_gateend = distance_gate + distance_gatespacing;
|
||||
|
||||
let a = [(distance_gate * az.cos()) as f32, (distance_gate * az.sin()) as f32, 0.0];
|
||||
let b = [(distance_gate * bz.cos()) as f32, (distance_gate * bz.sin()) as f32, 0.0];
|
||||
let c = [(d_gateend * az.cos()) as f32, (d_gateend * az.sin()) as f32, 0.0];
|
||||
let d = [(d_gateend * bz.cos()) as f32, (d_gateend * bz.sin()) as f32, 0.0];
|
||||
|
||||
// C--D
|
||||
// | |
|
||||
// A--B
|
||||
/*
|
||||
(x0, y1) -- (x1, y1)
|
||||
| |
|
||||
(x0, y0) -- (x1, y0)
|
||||
|
||||
*/
|
||||
|
||||
let vertex_a = Vertex { position: a, color: color_scheme(self.scope_mode, *value) };
|
||||
let vertex_b = Vertex { position: b, color: color_scheme(self.scope_mode, *value) };
|
||||
let vertex_c = Vertex { position: c, color: color_scheme(self.scope_mode, *value) };
|
||||
let vertex_d = Vertex { position: d, color: color_scheme(self.scope_mode, *value) };
|
||||
|
||||
let vindex_a = self.wgpu.verticies.len();
|
||||
self.wgpu.verticies.push(vertex_a);
|
||||
let vindex_b = self.wgpu.verticies.len();
|
||||
self.wgpu.verticies.push(vertex_b);
|
||||
let vindex_c = self.wgpu.verticies.len();
|
||||
self.wgpu.verticies.push(vertex_c);
|
||||
let vindex_d = self.wgpu.verticies.len();
|
||||
self.wgpu.verticies.push(vertex_d);
|
||||
|
||||
// ABC <-> BCD
|
||||
|
||||
self.wgpu.indicies.extend_from_slice(&[vindex_a as u16, vindex_b as u16, vindex_c as u16]);
|
||||
//self.wgpu.indicies.extend_from_slice(&[vindex_a as u16, vindex_d as u16, vindex_b as u16]);
|
||||
}
|
||||
azimuth += azimuth_spacing;
|
||||
}
|
||||
distance_gate += distance_gatespacing;
|
||||
}
|
||||
info!("Radar data triangled - {} verticies {} indicies", self.wgpu.verticies.len(), self.wgpu.indicies.len());
|
||||
}
|
||||
|
||||
|
||||
*/
|
||||
pub fn render(&mut self) -> Result<(), SurfaceError> {
|
||||
let output = self.wgpu.surface.get_current_texture()?;
|
||||
let view = output
|
||||
|
@ -258,160 +418,29 @@ impl ScopeState {
|
|||
label: Some("Render Encoder"),
|
||||
});
|
||||
|
||||
let physical_width = (self.wgpu.surface_config.width as f64 * self.wgpu.window.scale_factor()) as f32;
|
||||
let physical_height =
|
||||
(self.wgpu.surface_config.height as f64 * self.wgpu.window.scale_factor()) as f32;
|
||||
|
||||
let mut verticies = vec![];
|
||||
let mut indicies = vec![];
|
||||
|
||||
// ACTUAL DATA RENDERING
|
||||
if let Some(ar2) = &self.ar2 {
|
||||
let px_per_km = 1.0 / (250.0 * 2.0);
|
||||
|
||||
let radials = ar2.elevations.get(&self.selected_elevation).unwrap();
|
||||
|
||||
for radial in radials {
|
||||
let distance_firstgate_km = (radial.available_data.get(self.scope_mode.rname()).expect("selected mode is missing!").gdm.data_moment_range as f64) / 1000.0;
|
||||
let distance_gatespacing_km = (radials[0].available_data.get(self.scope_mode.rname()).expect("selected mode is missing!").gdm.data_moment_range_sample_interval as f64) / 1000.0;
|
||||
|
||||
let mut azimuth = radial.header.azimuth_angle as f64;
|
||||
|
||||
let azimuth_spacing = match radial.header.azimuth_resolution_spacing {
|
||||
1 => 0.5,
|
||||
_ => 1.0,
|
||||
};
|
||||
|
||||
// line width
|
||||
// line cap
|
||||
|
||||
let gates = radial
|
||||
.available_data
|
||||
.get(self.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 {
|
||||
let distance_gate_km = distance_firstgate_km + (distance_gatespacing_km * num as f64);
|
||||
let az = azimuth * (PI / 180.0);
|
||||
let bz = (azimuth + azimuth_spacing) * (PI / 180.0);
|
||||
|
||||
let d_gateend = distance_gate_km + distance_gatespacing_km;
|
||||
|
||||
let a = [(distance_gate_km * az.cos()) as f32 * px_per_km, (distance_gate_km * az.sin()) as f32 * px_per_km, 0.0];
|
||||
let b = [(distance_gate_km * bz.cos()) as f32 * px_per_km, (distance_gate_km * bz.sin()) as f32 * px_per_km, 0.0];
|
||||
let c = [(d_gateend * az.cos()) as f32 * px_per_km, (d_gateend * az.sin()) as f32 * px_per_km, 0.0];
|
||||
let d = [(d_gateend * bz.cos()) as f32 * px_per_km, (d_gateend * bz.sin()) as f32 * px_per_km, 0.0];
|
||||
|
||||
// C--D
|
||||
// | |
|
||||
// A--B
|
||||
/*
|
||||
(x0, y1) -- (x1, y1)
|
||||
| |
|
||||
(x0, y0) -- (x1, y0)
|
||||
|
||||
*/
|
||||
|
||||
let vertex_a = Vertex { position: a, color: color_scheme(self.scope_mode, *value) };
|
||||
let vertex_b = Vertex { position: b, color: color_scheme(self.scope_mode, *value) };
|
||||
let vertex_c = Vertex { position: c, color: color_scheme(self.scope_mode, *value) };
|
||||
let vertex_d = Vertex { position: d, color: color_scheme(self.scope_mode, *value) };
|
||||
|
||||
let vindex_a = verticies.len();
|
||||
verticies.push(vertex_a);
|
||||
let vindex_b = verticies.len();
|
||||
verticies.push(vertex_b);
|
||||
let vindex_c = verticies.len();
|
||||
verticies.push(vertex_c);
|
||||
let vindex_d = verticies.len();
|
||||
verticies.push(vertex_d);
|
||||
|
||||
// ABC <-> BCD
|
||||
|
||||
indicies.extend_from_slice(&[vindex_a, vindex_b, vindex_c]);
|
||||
indicies.extend_from_slice(&[vindex_b, vindex_c, vindex_d]);
|
||||
|
||||
/*
|
||||
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();
|
||||
|
||||
*/
|
||||
}
|
||||
|
||||
azimuth += azimuth_spacing;
|
||||
if self.wgpu.buffers_need_reinitialization {
|
||||
self.wgpu.buffers_need_reinitialization = false;
|
||||
self.wgpu.vertex_buffer.destroy();
|
||||
let new_vertex_buffer = self.wgpu.device.create_buffer_init(
|
||||
&wgpu::util::BufferInitDescriptor {
|
||||
label: Some("Vertex Buffer"),
|
||||
contents: bytemuck::cast_slice(self.wgpu.verticies.as_slice()),
|
||||
usage: wgpu::BufferUsages::VERTEX,
|
||||
}
|
||||
}
|
||||
);
|
||||
self.wgpu.vertex_buffer = new_vertex_buffer;
|
||||
|
||||
self.wgpu.index_buffer.destroy();
|
||||
let new_index_buffer = self.wgpu.device.create_buffer_init(
|
||||
&wgpu::util::BufferInitDescriptor {
|
||||
label: Some("Index Buffer"),
|
||||
contents: bytemuck::cast_slice(self.wgpu.indicies.as_slice()),
|
||||
usage: wgpu::BufferUsages::INDEX,
|
||||
}
|
||||
);
|
||||
self.wgpu.index_buffer = new_index_buffer;
|
||||
}
|
||||
|
||||
info!("Radar data tesselated - {} verticies {} indicies", verticies.len(), indicies.len());
|
||||
info!("{:?}", verticies.get(0).map(|u| u.position).unwrap_or([0.0, 0.0, 0.0]));
|
||||
|
||||
self.wgpu.vertex_buffer.destroy();
|
||||
let new_vertex_buffer = self.wgpu.device.create_buffer_init(
|
||||
&wgpu::util::BufferInitDescriptor {
|
||||
label: Some("Vertex Buffer"),
|
||||
contents: bytemuck::cast_slice(verticies.as_slice()),
|
||||
usage: wgpu::BufferUsages::VERTEX,
|
||||
}
|
||||
);
|
||||
self.wgpu.vertex_buffer = new_vertex_buffer;
|
||||
|
||||
self.wgpu.index_buffer.destroy();
|
||||
let new_index_buffer = self.wgpu.device.create_buffer_init(
|
||||
&wgpu::util::BufferInitDescriptor {
|
||||
label: Some("Index Buffer"),
|
||||
contents: bytemuck::cast_slice(indicies.as_slice()),
|
||||
usage: wgpu::BufferUsages::INDEX,
|
||||
}
|
||||
);
|
||||
self.wgpu.index_buffer = new_index_buffer;
|
||||
|
||||
// LOCK BLOCK
|
||||
{
|
||||
|
@ -438,7 +467,7 @@ impl ScopeState {
|
|||
render_pass.set_pipeline(&self.wgpu.pipeline);
|
||||
render_pass.set_vertex_buffer(0, self.wgpu.vertex_buffer.slice(..));
|
||||
render_pass.set_index_buffer(self.wgpu.index_buffer.slice(..), wgpu::IndexFormat::Uint16); // 1.
|
||||
render_pass.draw_indexed(0..(indicies.len() as u32), 0, 0..1);
|
||||
render_pass.draw_indexed(0..(self.wgpu.indicies.len() as u32), 0, 0..1);
|
||||
|
||||
}
|
||||
|
||||
|
@ -718,4 +747,5 @@ impl ScopeState {
|
|||
|
||||
pub struct Preferences {
|
||||
pub fcs: f32,
|
||||
pub range: f32
|
||||
}
|
||||
|
|
|
@ -7,4 +7,6 @@ edition = "2021"
|
|||
|
||||
[dependencies]
|
||||
simple_logger = "4"
|
||||
nexrad2 = { version = "0.1", path = "../nexrad2", default-features = false, features = ["bzip-impl-libbzip2"] }
|
||||
nexrad2 = { version = "0.1", path = "../nexrad2", default-features = false, features = ["bzip-impl-libbzip2"] }
|
||||
rtwx-render = { version = "0.1", path = "../rtwx-render" }
|
||||
image = "0.24"
|
Binary file not shown.
After Width: | Height: | Size: 889 KiB |
|
@ -1,12 +1,33 @@
|
|||
use std::fs;
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use image::{ImageBuffer, RgbImage};
|
||||
use nexrad2::parse_nx2_chunk;
|
||||
use rtwx_render::NexradRenderExt;
|
||||
|
||||
fn main() {
|
||||
simple_logger::init().unwrap();
|
||||
let test = std::env::args().nth(1).unwrap();
|
||||
simple_logger::init_with_env().unwrap();
|
||||
let file = std::env::args().nth(1).unwrap();
|
||||
|
||||
let mut data = File::open(test).unwrap();
|
||||
let mut data = File::open(file).unwrap();
|
||||
|
||||
parse_nx2_chunk(&mut data).unwrap();
|
||||
let chunk = parse_nx2_chunk(&mut data).unwrap();
|
||||
|
||||
let elevation: usize = std::env::args().nth(2).unwrap().parse().unwrap();
|
||||
let mode = std::env::args().nth(3).unwrap();
|
||||
let img_size: usize = std::env::args().nth(4).unwrap().parse().unwrap();
|
||||
|
||||
let img = chunk.render(elevation, mode.as_str(), img_size).unwrap();
|
||||
|
||||
let out_file = std::env::args().nth(5).unwrap();
|
||||
|
||||
let mut img_rgb: RgbImage = ImageBuffer::new(img_size as u32, img_size as u32);
|
||||
for x in 0..img_size {
|
||||
for y in 0..img_size {
|
||||
let pixel = img[y * img_size + x];
|
||||
img_rgb.put_pixel(x as u32, y as u32, image::Rgb([pixel.0, pixel.1, pixel.2]));
|
||||
}
|
||||
}
|
||||
|
||||
img_rgb.save(out_file).unwrap();
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
[package]
|
||||
name = "rtwx-render"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
nexrad2 = { version = "0.1", path = "../nexrad2" }
|
|
@ -0,0 +1,233 @@
|
|||
use nexrad2::message31::{MOMENT_DATA_BELOW_THRESHOLD, MOMENT_DATA_FOLDED};
|
||||
|
||||
pub fn correlation_coefficient(val: f32) -> [f32; 3] {
|
||||
let gradient = [
|
||||
(0.275, [0.0, 0.0, 0.0]),
|
||||
(0.35, [169.0, 169.0, 169.0]),
|
||||
(0.4, [128.0, 128.0, 128.0]),
|
||||
(0.5, [192.0, 192.0, 192.0]),
|
||||
(0.6, [25.0, 25.0, 112.0]),
|
||||
(0.7, [0.0, 0.0, 139.0]),
|
||||
(0.8, [0.0, 0.0, 255.0]),
|
||||
(0.91, [0.0, 128.0, 0.0]),
|
||||
(0.92, [154.0, 205.0, 50.0]),
|
||||
(0.93, [107.0, 142.0, 35.0]),
|
||||
(0.94, [255.0, 255.0, 0.0]),
|
||||
(0.95, [255.0, 215.0, 0.0]),
|
||||
(0.96, [255.0, 165.0, 0.0]),
|
||||
(0.97, [255.0, 69.0, 0.0]),
|
||||
(0.98, [255.0, 0.0, 0.0]),
|
||||
(0.99, [178.0, 34.0, 34.0]),
|
||||
(1.0, [128.0, 0.0, 0.0]),
|
||||
(1.01, [139.0, 0.0, 139.0]),
|
||||
(1.02, [128.0, 0.0, 128.0]),
|
||||
(1.03, [199.0, 21.0, 133.0]),
|
||||
(1.045, [255.0, 192.0, 203.0]),
|
||||
(1.05, [255.0, 240.0, 245.0]),
|
||||
];
|
||||
|
||||
for (threshold, color) in gradient {
|
||||
if val < threshold {
|
||||
return color;
|
||||
}
|
||||
}
|
||||
|
||||
[255.0, 255.0, 255.0]
|
||||
}
|
||||
|
||||
pub fn spectrum_width(val: f32) -> [f32; 3] {
|
||||
let gradient = [
|
||||
(1.0, [0.0, 0.0, 0.0]),
|
||||
(2.0, [0x22 as f32, 0x22 as f32, 0x22 as f32]),
|
||||
(3.0, [0x33 as f32, 0x33 as f32, 0x33 as f32]),
|
||||
(4.0, [0x44 as f32, 0x44 as f32, 0x44 as f32]),
|
||||
(5.0, [0x55 as f32, 0x55 as f32, 0x55 as f32]),
|
||||
(6.0, [0x66 as f32, 0x66 as f32, 0x66 as f32]),
|
||||
(7.0, [0x77 as f32, 0x77 as f32, 0x77 as f32]),
|
||||
(8.0, [0x88 as f32, 0x88 as f32, 0x88 as f32]),
|
||||
(9.0, [0x99 as f32, 0x99 as f32, 0x99 as f32]),
|
||||
(10.0, [222.0, 184.0, 135.0]),
|
||||
(11.0, [244.0, 164.0, 96.0]),
|
||||
(12.0, [255.0, 215.0, 0.0]),
|
||||
(13.0, [255.0, 165.0, 0.0]),
|
||||
(15.0, [255.0, 69.0, 0.0]),
|
||||
(17.0, [255.0, 0.0, 0.0]),
|
||||
(19.0, [178.0, 34.0, 34.0]),
|
||||
(23.0, [139.0, 0.0, 0.0]),
|
||||
(25.0, [255.0, 105.0, 180.0]),
|
||||
(27.0, [255.0, 0.0, 255.0]),
|
||||
(30.0, [230.0, 230.0, 250.0]),
|
||||
(32.0, [255.0, 255.0, 255.0]),
|
||||
(35.0, [255.0, 255.0, 0.0]),
|
||||
(60.0, [0.0, 255.0, 0.0]),
|
||||
(1000.0, [128.0, 0.0, 128.0]),
|
||||
];
|
||||
|
||||
for (threshold, color) in gradient {
|
||||
if val < threshold {
|
||||
return color;
|
||||
}
|
||||
}
|
||||
|
||||
[255.0, 255.0, 255.0]
|
||||
}
|
||||
|
||||
pub fn differential_reflectivity(val: f32) -> [f32; 3] {
|
||||
let gradient = [
|
||||
(-3.0, [0.0, 0.0, 0.0]),
|
||||
(-1.0, [0x33 as f32, 0x33 as f32, 0x33 as f32]),
|
||||
(-0.5, [0x66 as f32, 0x66 as f32, 0x66 as f32]),
|
||||
(0.1, [0x99 as f32, 0x99 as f32, 0x99 as f32]),
|
||||
(0.0, [0xcc as f32, 0xcc as f32, 0xcc as f32]),
|
||||
(0.1, [255.0, 255.0, 255.0]),
|
||||
(0.25, [0.0, 0.0, 128.0]),
|
||||
(0.5, [0.0, 0.0, 255.0]),
|
||||
(0.75, [0.0, 191.0, 255.0]),
|
||||
(1.0, [0.0, 255.0, 255.0]),
|
||||
(1.25, [102.0, 205.0, 170.0]),
|
||||
(1.5, [0.0, 255.0, 0.0]),
|
||||
(1.75, [154.0, 205.0, 50.0]),
|
||||
(2.0, [255.0, 255.0, 0.0]),
|
||||
(2.5, [255.0, 215.0, 0.0]),
|
||||
(3.0, [255.0, 165.0, 0.0]),
|
||||
(4.0, [255.0, 69.0, 0.0]),
|
||||
(5.0, [255.0, 0.0, 0.0]),
|
||||
(6.0, [128.0, 0.0, 0.0]),
|
||||
(7.0, [255.0, 105.0, 180.0]),
|
||||
(10.0, [255.0, 192.0, 203.0]),
|
||||
(999.0, [255.0, 255.0, 255.0]),
|
||||
];
|
||||
|
||||
for (threshold, color) in gradient {
|
||||
if val < threshold {
|
||||
return color;
|
||||
}
|
||||
}
|
||||
|
||||
[255.0, 255.0, 255.0]
|
||||
}
|
||||
|
||||
pub fn dbz_noaa(dbz: f32) -> [f32; 3] {
|
||||
if dbz < 5.0 || dbz == MOMENT_DATA_FOLDED {
|
||||
[0x00 as f32, 0x00 as f32, 0x00 as f32]
|
||||
} else if dbz >= 5.0 && dbz < 10.0 {
|
||||
[0x40 as f32, 0xe8 as f32, 0xe3 as f32]
|
||||
} else if dbz >= 10.0 && dbz < 15.0 {
|
||||
[0x26 as f32, 0xa4 as f32, 0xfa as f32]
|
||||
} else if dbz >= 15.0 && dbz < 20.0 {
|
||||
[0x00 as f32, 0x30 as f32, 0xed as f32]
|
||||
} else if dbz >= 20.0 && dbz < 25.0 {
|
||||
[0x49 as f32, 0xfb as f32, 0x3e as f32]
|
||||
} else if dbz >= 25.0 && dbz < 30.0 {
|
||||
[0x36 as f32, 0xc2 as f32, 0x2e as f32]
|
||||
} else if dbz >= 30.0 && dbz < 35.0 {
|
||||
[0x27 as f32, 0x8c as f32, 0x1e as f32]
|
||||
} else if dbz >= 35.0 && dbz < 40.0 {
|
||||
[0xfe as f32, 0xf5 as f32, 0x43 as f32]
|
||||
} else if dbz >= 40.0 && dbz < 45.0 {
|
||||
[0xeb as f32, 0xb4 as f32, 0x33 as f32]
|
||||
} else if dbz >= 45.0 && dbz < 50.0 {
|
||||
[0xf6 as f32, 0x95 as f32, 0x2e as f32]
|
||||
} else if dbz >= 50.0 && dbz < 55.0 {
|
||||
[0xf8 as f32, 0x0a as f32, 0x26 as f32]
|
||||
} else if dbz >= 55.0 && dbz < 60.0 {
|
||||
[0xcb as f32, 0x05 as f32, 0x16 as f32]
|
||||
} else if dbz >= 60.0 && dbz < 65.0 {
|
||||
[0xa9 as f32, 0x08 as f32, 0x13 as f32]
|
||||
} else if dbz >= 65.0 && dbz < 70.0 {
|
||||
[0xee as f32, 0x34 as f32, 0xfa as f32]
|
||||
} else if dbz >= 79.0 && dbz < 75.0 {
|
||||
[0x91 as f32, 0x61 as f32, 0xc4 as f32]
|
||||
} else {
|
||||
[255.0, 255.0, 255.0]
|
||||
}
|
||||
}
|
||||
|
||||
pub fn velocity(vel: f32) -> [f32; 3] {
|
||||
if vel == MOMENT_DATA_FOLDED {
|
||||
return [0x69 as f32, 0x1a as f32, 0xc1 as f32];
|
||||
}
|
||||
|
||||
let colors = [
|
||||
[0xf9, 0x14, 0x73],
|
||||
[0xaa, 0x10, 0x79],
|
||||
[0x6e, 0x0e, 0x80],
|
||||
[0x2e, 0x0e, 0x84],
|
||||
[0x15, 0x1f, 0x93],
|
||||
[0x23, 0x6f, 0xb3],
|
||||
[0x41, 0xda, 0xdb],
|
||||
[0x66, 0xe1, 0xe2],
|
||||
[0x9e, 0xe8, 0xea],
|
||||
[0x58, 0xfa, 0x63],
|
||||
[0x31, 0xe3, 0x2b],
|
||||
// "#21BE0A", // 35
|
||||
[0x24, 0xaa, 0x1f],
|
||||
[0x19, 0x76, 0x13],
|
||||
[0x45, 0x67, 0x42],
|
||||
[0x63, 0x4f, 0x50],
|
||||
[0x6e, 0x2e, 0x39],
|
||||
[0x7f, 0x03, 0x0c],
|
||||
[0xb6, 0x07, 0x16],
|
||||
// "#C5000D", // 35
|
||||
[0xf3, 0x22, 0x45],
|
||||
[0xf6, 0x50, 0x8a],
|
||||
[0xfb, 0x8b, 0xbf],
|
||||
[0xfd, 0xde, 0x93],
|
||||
[0xfc, 0xb4, 0x70],
|
||||
[0xfa, 0x81, 0x4b],
|
||||
[0xdd, 0x60, 0x3c],
|
||||
[0xb7, 0x45, 0x2d],
|
||||
[0x93, 0x2c, 0x20],
|
||||
[0x71, 0x16, 0x14],
|
||||
[0x52, 0x01, 0x06],
|
||||
];
|
||||
|
||||
let i = scale_int(
|
||||
(vel.floor()) as i32,
|
||||
140,
|
||||
-140,
|
||||
(colors.len() - 1) as i32,
|
||||
0,
|
||||
);
|
||||
|
||||
[colors[i as usize][0] as f32, colors[i as usize][1] as f32, colors[i as usize][2] as f32]
|
||||
}
|
||||
|
||||
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: &str, value: f32) -> [f32; 3] {
|
||||
if value == MOMENT_DATA_BELOW_THRESHOLD {
|
||||
return [rgb_to_srgb_float_individual_unscaled(0x69 as f32), rgb_to_srgb_float_individual_unscaled(0x1a as f32), rgb_to_srgb_float_individual_unscaled(0xc1 as f32)];
|
||||
}
|
||||
let rgb = match product {
|
||||
"REF" => dbz_noaa(value),
|
||||
"VEL" => velocity(value),
|
||||
"SW" => spectrum_width(value),
|
||||
"ZDR" => differential_reflectivity(value),
|
||||
"PHI" => correlation_coefficient(value),
|
||||
"RHO" => correlation_coefficient(value),
|
||||
"CFP" => dbz_noaa(value),
|
||||
_ => panic!()
|
||||
};
|
||||
|
||||
rgb
|
||||
//[rgb_to_srgb_float_individual_unscaled(rgb[0]), rgb_to_srgb_float_individual_unscaled(rgb[1]), rgb_to_srgb_float_individual_unscaled(rgb[2])]
|
||||
}
|
||||
|
||||
pub fn rgb_to_srgb_float_individual_unscaled(rgb_color: f32) -> f32 {
|
||||
((rgb_color / 255.0 + 0.055) / 1.055).powf(2.4)
|
||||
}
|
||||
|
||||
pub fn rgb_to_srgb(c: u32, a: f32) -> [f32; 4] {
|
||||
let f = |xu: u32| {
|
||||
let x = (xu & 0xFF) as f32 / 255.0;
|
||||
if x > 0.04045 {
|
||||
((x + 0.055) / 1.055).powf(2.4)
|
||||
} else {
|
||||
x / 12.92
|
||||
}
|
||||
};
|
||||
[f(c >> 16), f(c >> 8), f(c), a]
|
||||
}
|
|
@ -0,0 +1,117 @@
|
|||
use std::error::Error;
|
||||
use std::f32::consts::PI;
|
||||
use std::fmt::{Display, Formatter};
|
||||
use nexrad2::message31::MOMENT_DATA_BELOW_THRESHOLD;
|
||||
use crate::colors::color_scheme;
|
||||
|
||||
pub mod colors;
|
||||
|
||||
pub trait NexradRenderExt {
|
||||
fn render(&self, elevation: usize, product: &str, image_size: usize) -> Result<Vec<(u8, u8, u8)>, RenderError>;
|
||||
}
|
||||
|
||||
impl NexradRenderExt for nexrad2::Nexrad2Chunk {
|
||||
fn render(&self, elevation: usize, product: &str, image_size: usize) -> Result<Vec<(u8, u8, u8)>, RenderError> {
|
||||
let mut pixel_data = vec![[0.0, 0.0, 0.0]; image_size * image_size];
|
||||
|
||||
let center = (image_size as f64) / 2.0;
|
||||
let px_per_km = (image_size as f64) / 2.0 / 920.0;
|
||||
|
||||
let radials = self.elevations.get(&elevation).ok_or(RenderError::ElevationMissing)?;
|
||||
|
||||
let moment_range = radials[0].available_data.get("REF").unwrap().gdm.data_moment_range;
|
||||
let first_gate_px = moment_range as f32 / 1000.0 * px_per_km as f32;
|
||||
let gate_interval_km = radials[0].available_data.get("REF").unwrap().gdm.data_moment_range_sample_interval as f64 / 1000.0;
|
||||
println!("{}", gate_interval_km);
|
||||
let gate_width_px = gate_interval_km * px_per_km as f64;
|
||||
println!("{} {}", px_per_km, gate_width_px);
|
||||
|
||||
for radial in radials {
|
||||
let mut azimuth_angle = radial.header.azimuth_angle - 90.0;
|
||||
if azimuth_angle < 0.0 {
|
||||
azimuth_angle += 360.0;
|
||||
}
|
||||
|
||||
let azimuth_spacing = if radial.header.azimuth_resolution_spacing == 1 { 0.5 } else { 1.0 };
|
||||
|
||||
let mut azimuth = azimuth_angle.floor();
|
||||
|
||||
if azimuth < (azimuth_angle + azimuth_spacing).floor() {
|
||||
azimuth += azimuth_spacing;
|
||||
}
|
||||
|
||||
println!("azimuth: {}", azimuth);
|
||||
|
||||
let start_angle = azimuth * (PI / 180.0);
|
||||
|
||||
println!("azimuth, radians: {}", start_angle);
|
||||
|
||||
let mut distance = first_gate_px;
|
||||
|
||||
let data_moment = radials[0].available_data.get(product).ok_or(RenderError::ProductMissing)?;
|
||||
|
||||
let gates = data_moment.scaled_data();
|
||||
|
||||
for gate in gates {
|
||||
if gate != MOMENT_DATA_BELOW_THRESHOLD {
|
||||
let mut pixel_x = (center as f32 + start_angle.cos() * distance).round() as usize;
|
||||
let mut pixel_y = (center as f32 + start_angle.sin() * distance).round() as usize;
|
||||
|
||||
if pixel_x >= image_size {
|
||||
pixel_x = image_size-1;
|
||||
}
|
||||
if pixel_y >= image_size {
|
||||
pixel_y = image_size-1;
|
||||
}
|
||||
|
||||
if pixel_y * image_size + pixel_x > (image_size * image_size) {
|
||||
println!("{} {}", pixel_x, pixel_y);
|
||||
}
|
||||
|
||||
pixel_data[pixel_y * image_size + pixel_x] = color_scheme(product, gate);
|
||||
//println!("{} {} {}", pixel_x, pixel_y, gate);
|
||||
//println!("not below threshold! {} {}", pixel_x, pixel_y);
|
||||
//println!("{}", gate_width_px);
|
||||
} else {
|
||||
let mut pixel_x = (center as f32 + start_angle.cos() * distance).round() as usize;
|
||||
let mut pixel_y = (center as f32 + start_angle.sin() * distance).round() as usize;
|
||||
|
||||
if pixel_x >= image_size {
|
||||
pixel_x = image_size-1;
|
||||
}
|
||||
if pixel_y >= image_size {
|
||||
pixel_y = image_size-1;
|
||||
}
|
||||
|
||||
if pixel_y * image_size + pixel_x > (image_size * image_size) {
|
||||
println!("{} {}", pixel_x, pixel_y);
|
||||
}
|
||||
|
||||
pixel_data[pixel_y * image_size + pixel_x] = [0.3, 0.2, 0.1];
|
||||
//println!("not below threshold! {} {}", pixel_x, pixel_y);
|
||||
//println!("{}", gate_width_px);
|
||||
}
|
||||
|
||||
distance += gate_width_px as f32;
|
||||
azimuth += azimuth_spacing;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(pixel_data.iter().map(|u| ((u[0] * 255.0).round() as u8, (u[1] * 255.0).round() as u8, (u[2] * 255.0).round() as u8)).collect())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum RenderError {
|
||||
ElevationMissing,
|
||||
ProductMissing
|
||||
}
|
||||
impl Display for RenderError {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::ElevationMissing => write!(f, "elevation missing"),
|
||||
Self::ProductMissing => write!(f, "product missing")
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Error for RenderError {}
|
Loading…
Reference in New Issue