diff --git a/.idea/rtwx.iml b/.idea/rtwx.iml
index bbe0211..361925b 100644
--- a/.idea/rtwx.iml
+++ b/.idea/rtwx.iml
@@ -6,6 +6,7 @@
+
diff --git a/Cargo.toml b/Cargo.toml
index 7ee8d5d..8bdd420 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -3,7 +3,8 @@ members = [
"nexrad2",
"rtwx",
"nexrad-browser",
- "nxar2"
+ "nxar2",
+ "rtwx-desktop"
]
resolver = "2"
diff --git a/docs/README.md b/docs/README.md
index b10f6b3..19eb7ac 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -33,10 +33,18 @@ To open RTWX, open [rtwx.e3t.cc](https://rtwx.e3t.cc) in your browser. After a b
## The Scope
-After you initially load RTWX, the scope will appear blank. This is because text is hidden by default. To show it, enter the command `` `T `` to toggle `` ` `` text `T`.
+After you initially load RTWX, the scope will appear blank.
+
+![Empty Scope](empty_scope.png)
+
+This is because text is hidden by default. To show it, enter the command `` `T `` to toggle `` ` `` text `T`.
+
+![Inoperative Scope](scope_inop.png)
The scope is broken down into 5 main parts.
+![Marked Up Scope](scope_inop_markedup.png)
+
### Status Information Area
In the top left of the scope is the *Status Information Area.*
@@ -69,11 +77,60 @@ The command buffer will contain any commands you are entering and responses to t
### Site Information Area
-*Reserved*
+In the top right of the scope is the Site Information Area, which contains information about the currently loaded radar file.
+
+When inoperative, the Site Information Area will report that all information is "unavailable".
+
+![Site Information Area: Inoperative](site_information_area_inop.png)
+
+Once loaded, it will show the following data:
+
+![Site Information Area: Filled KOTX](site_information_area_kotx.png)
+
+- the ICAO of the current radar site
+- the VCP number in use
+- a plain-language description of the VCP
+- the site delay: how long ago was the current radar file captured (HH:MM:SS)
### Mode Selector
-*Reserved*
+The Mode Selector is located on the midright of the scope. It contains information about the current Mode and Elevation that the scope is displaying.
+
+The Mode Selector is broken down into two parts:
+
+#### Radar Product Mode
+
+The Radar Product Mode display shows which radar product is currently being displayed. While the scope is inoperative, it will display `>RADR INOP<` in place of mode options:
+
+![Radar Product Mode: Inoperative](mode_inop.png)
+
+Once radar data is being received, it will show a list of the products available for the currently selected elevation:
+
+![Radar Product Mode: Filled](mode_filled.png)
+
+The currently selected product is shown highlighted in white, with angle brackets around it.
+Typically, `REF` (Reflectivity) will be selected by default.
+
+Not all products are available for every elevation. For example, most commonly Velocity and Spectrum Width will not be present:
+
+![Radar Product Mode: Partial](mode_partial.png)
+
+To change what mode is being displayed, use the `MODE SET` command.
+
+#### Radar Elevation
+
+The Radar Elevation display, below the Radar Product Mode display, shows which elevations are available in the current radar data. While the scope is inoperative, it will display `ELEVATION INFO UNAVAILABLE` in place of elevations:
+
+![Radar Elevation: Inoperative](elevations_inop.png)
+
+Once radar data is being received, it will show a list of the elevations available:
+
+![Radar Elevation: Filled](elevations_filled.png)
+
+The currently selected elevation will be highlighted in white, and have angle brackets around it.
+Typically, elevation `001` will be selected by default.
+
+To change which elevation is being displayed, use the `ELEVATION SET` command. Changing elevation will automatically reset the scope to `REF` (Reflectivity) mode.
### Radar Scope
diff --git a/docs/elevations_filled.png b/docs/elevations_filled.png
new file mode 100644
index 0000000..75a31c2
Binary files /dev/null and b/docs/elevations_filled.png differ
diff --git a/docs/elevations_inop.png b/docs/elevations_inop.png
new file mode 100644
index 0000000..71cc524
Binary files /dev/null and b/docs/elevations_inop.png differ
diff --git a/docs/empty_scope.png b/docs/empty_scope.png
new file mode 100644
index 0000000..c26c75b
Binary files /dev/null and b/docs/empty_scope.png differ
diff --git a/docs/mode_filled.png b/docs/mode_filled.png
new file mode 100644
index 0000000..01b6cfa
Binary files /dev/null and b/docs/mode_filled.png differ
diff --git a/docs/mode_inop.png b/docs/mode_inop.png
new file mode 100644
index 0000000..a27d3c0
Binary files /dev/null and b/docs/mode_inop.png differ
diff --git a/docs/mode_partial.png b/docs/mode_partial.png
new file mode 100644
index 0000000..b3de89f
Binary files /dev/null and b/docs/mode_partial.png differ
diff --git a/docs/scope_inop.png b/docs/scope_inop.png
new file mode 100644
index 0000000..f83271a
Binary files /dev/null and b/docs/scope_inop.png differ
diff --git a/docs/scope_inop_markedup.png b/docs/scope_inop_markedup.png
new file mode 100644
index 0000000..ae33041
Binary files /dev/null and b/docs/scope_inop_markedup.png differ
diff --git a/docs/site_information_area_inop.png b/docs/site_information_area_inop.png
new file mode 100644
index 0000000..785b946
Binary files /dev/null and b/docs/site_information_area_inop.png differ
diff --git a/docs/site_information_area_kotx.png b/docs/site_information_area_kotx.png
new file mode 100644
index 0000000..5675361
Binary files /dev/null and b/docs/site_information_area_kotx.png differ
diff --git a/nexrad-browser/Cargo.toml b/nexrad-browser/Cargo.toml
index c9906cf..b1f399a 100644
--- a/nexrad-browser/Cargo.toml
+++ b/nexrad-browser/Cargo.toml
@@ -27,6 +27,7 @@ wgpu = { version = "0.18", features = ["webgl"] }
raw-window-handle = "0.5"
wasm-bindgen-futures = "0.4"
bytemuck = { version = "1", features = ["derive"]}
+profiling = "1"
# Text rendering #
wgpu_glyph = { git = "https://github.com/hecrj/wgpu_glyph" }
diff --git a/nexrad-browser/src/lib.rs b/nexrad-browser/src/lib.rs
index e472120..f54821b 100644
--- a/nexrad-browser/src/lib.rs
+++ b/nexrad-browser/src/lib.rs
@@ -122,7 +122,7 @@ pub async fn __nxrd_browser_init(w: u32, h: u32) -> AbiProxy {
if event.logical_key == Key::Named(NamedKey::Enter) {
let command = scope_state.command_buf.clone();
scope_state.command_buf = String::new();
- exec(&mut scope_state, command);
+ exec(&mut scope_state, command.replace("\n", " "));
return;
}
diff --git a/nexrad-browser/src/rendering/scope.rs b/nexrad-browser/src/rendering/scope.rs
index c790a81..50cd516 100644
--- a/nexrad-browser/src/rendering/scope.rs
+++ b/nexrad-browser/src/rendering/scope.rs
@@ -4,6 +4,7 @@ use nexrad2::Nexrad2Chunk;
use std::iter;
use chrono::Utc;
+use itertools::Itertools;
use log::info;
use wasm_bindgen::JsCast;
@@ -22,9 +23,12 @@ use wgpu_glyph::{GlyphBrush, GlyphBrushBuilder, HorizontalAlign, Layout, Section
use winit::dpi::PhysicalSize;
use winit::event::WindowEvent;
use winit::window::Window;
+use crate::mode::Mode::{RadarInoperative, Reflectivity};
use crate::rendering::colors::rgb_to_srgb;
use crate::rendering::time::zulu;
use crate::rendering::vertex::{VERTICES};
+use crate::utils::parse_date;
+use crate::vcp::vcp_string;
pub struct ScopeState {
pub ar2: Option,
@@ -318,11 +322,11 @@ impl ScopeState {
}
}
- let command_base = (self.wgpu.surface_config.height / 3) as f32;
+ let one_third = (self.wgpu.surface_config.height / 3) as f32;
for (line_no, line) in self.command_buf.split('\n').enumerate() {
self.wgpu.glyph_brush.queue(Section {
- screen_position: (10.0, command_base + (line_height * line_no as f32)),
+ screen_position: (10.0, one_third + (line_height * line_no as f32)),
bounds: (self.wgpu.surface_config.width as f32, self.wgpu.surface_config.height as f32),
text: vec![Text::new(line)
.with_color(rgb_to_srgb(0x32cd32, 1.0))
@@ -340,6 +344,191 @@ impl ScopeState {
.with_scale(self.prefs.fcs)],
..Section::default()
}.with_layout(Layout::default().h_align(HorizontalAlign::Right)));
+
+ if let Some(ar2) = &self.ar2 {
+ self.wgpu.glyph_brush.queue(Section {
+ screen_position: (self.wgpu.surface_config.width as f32 - 10.0, 8.0 + line_height),
+ bounds: (self.wgpu.surface_config.width as f32, self.wgpu.surface_config.height as f32),
+ text: vec![Text::new(&format!("{} VCP {} {}", ar2.volume_header_record.icao,
+ ar2.meta_rda_status_data.vcp,
+ vcp_string(ar2.meta_rda_status_data.vcp)))
+ .with_color(rgb_to_srgb(0x32cd32, 1.0))
+ .with_scale(self.prefs.fcs)],
+ ..Section::default()
+ }.with_layout(Layout::default().h_align(HorizontalAlign::Right)));
+
+ 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() / 3600;
+
+ self.wgpu.glyph_brush.queue(Section {
+ screen_position: (self.wgpu.surface_config.width as f32 - 10.0, 8.0 + (line_height*2.0)),
+ bounds: (self.wgpu.surface_config.width as f32, self.wgpu.surface_config.height as f32),
+ text: vec![Text::new(&format!("SITE DELAY {:0>2}:{:0>2}:{:0>2}", hours, minutes, seconds))
+ .with_color(rgb_to_srgb(0x32cd32, 1.0))
+ .with_scale(self.prefs.fcs)],
+ ..Section::default()
+ }.with_layout(Layout::default().h_align(HorizontalAlign::Right)));
+ } else {
+ self.wgpu.glyph_brush.queue(Section {
+ screen_position: (self.wgpu.surface_config.width as f32 - 10.0, 8.0 + line_height),
+ bounds: (self.wgpu.surface_config.width as f32, self.wgpu.surface_config.height as f32),
+ text: vec![Text::new("SITE INFORMATION UNAVAILABLE")
+ .with_color(rgb_to_srgb(0x32cd32, 1.0))
+ .with_scale(self.prefs.fcs)],
+ ..Section::default()
+ }.with_layout(Layout::default().h_align(HorizontalAlign::Right)));
+ self.wgpu.glyph_brush.queue(Section {
+ screen_position: (self.wgpu.surface_config.width as f32 - 10.0, 8.0 + (line_height * 2.0)),
+ bounds: (self.wgpu.surface_config.width as f32, self.wgpu.surface_config.height as f32),
+ text: vec![Text::new("DELAY INFORMATION UNAVAILABLE")
+ .with_color(rgb_to_srgb(0x32cd32, 1.0))
+ .with_scale(self.prefs.fcs)],
+ ..Section::default()
+ }.with_layout(Layout::default().h_align(HorizontalAlign::Right)));
+ }
+
+ // mode info
+ self.wgpu.glyph_brush.queue(Section {
+ screen_position: (self.wgpu.surface_config.width as f32 - 10.0, one_third),
+ bounds: (self.wgpu.surface_config.width as f32, self.wgpu.surface_config.height as f32),
+ text: vec![Text::new("MODE")
+ .with_color(rgb_to_srgb(0x32cd32, 1.0))
+ .with_scale(self.prefs.fcs)],
+ ..Section::default()
+ }.with_layout(Layout::default().h_align(HorizontalAlign::Right)));
+
+ let modelines = [
+ [Mode::Reflectivity, Mode::Velocity, Mode::SpectrumWidth].as_slice(),
+ [
+ Mode::DifferentialReflectivity,
+ Mode::DifferentialPhase,
+ Mode::CorrelationCoefficient,
+ ]
+ .as_slice(),
+ [Mode::ClutterFilterPowerRemoved, Mode::RadarInoperative].as_slice(),
+ ];
+
+ let mut available_modes = vec![];
+
+ if let Some(ar2) = &self.ar2 {
+ let types = ar2.elevations.get(&self.selected_elevation).unwrap();
+ available_modes = types[0].available_data.keys().collect();
+ }
+
+ for (line_no, line) in modelines.iter().enumerate() {
+ let x = (self.wgpu.surface_config.width as f32 - 10.0);
+ let y = one_third + line_height * (line_no as f32 + 1.0);
+ 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 self.scope_mode == *item {
+ item.selected()
+ } else {
+ item.unselected()
+ };
+
+ if self.scope_mode == *item {
+ // display selected color
+ if *item == RadarInoperative {
+ self.wgpu.glyph_brush.queue(Section {
+ screen_position: (x, y),
+ bounds: (self.wgpu.surface_config.width as f32, self.wgpu.surface_config.height as f32),
+ text: vec![Text::new(">RADR INOP<")
+ .with_color(rgb_to_srgb(0xff0000, 1.0))
+ .with_scale(self.prefs.fcs)],
+ ..Section::default()
+ }.with_layout(Layout::default().h_align(HorizontalAlign::Right)));
+ continue;
+ }
+ } else if *item == RadarInoperative {
+ continue;
+ }
+
+ if available_modes.contains(&&item.rname().to_string()) {
+ self.wgpu.glyph_brush.queue(Section {
+ screen_position: (x, y),
+ bounds: (self.wgpu.surface_config.width as f32, self.wgpu.surface_config.height as f32),
+ text: vec![Text::new(&format!(" {}{}{}", pad_start, middle, pad_end))
+ .with_color(rgb_to_srgb(if self.scope_mode == *item { 0xdedede } else { 0x32cd32 }, 1.0))
+ .with_scale(self.prefs.fcs)],
+ ..Section::default()
+ }.with_layout(Layout::default().h_align(HorizontalAlign::Right)));
+ }
+ }
+ }
+
+ let four_sevenths = ((self.wgpu.surface_config.height / 7) * 4) as f32;
+
+ self.wgpu.glyph_brush.queue(Section {
+ screen_position: (self.wgpu.surface_config.width as f32 - 10.0, four_sevenths),
+ bounds: (self.wgpu.surface_config.width as f32, self.wgpu.surface_config.height as f32),
+ text: vec![Text::new("ELEVATIONS")
+ .with_color(rgb_to_srgb(0x32cd32, 1.0))
+ .with_scale(self.prefs.fcs)],
+ ..Section::default()
+ }.with_layout(Layout::default().h_align(HorizontalAlign::Right)));
+
+ if let Some(ar2) = &self.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 self.selected_elevation != *elevation_no {
+ let s = &format!(
+ " {:0>3} {}",
+ elevation_no,
+ " ".repeat(5 * (3 - (no_in_line % 4)))
+ );
+
+ self.wgpu.glyph_brush.queue(Section {
+ screen_position: (self.wgpu.surface_config.width as f32 - 10.0, four_sevenths + line_height * (line_no as f32 + 1.0)),
+ bounds: (self.wgpu.surface_config.width as f32, self.wgpu.surface_config.height as f32),
+ text: vec![Text::new(s)
+ .with_color(rgb_to_srgb(0x32cd32, 1.0))
+ .with_scale(self.prefs.fcs)],
+ ..Section::default()
+ }.with_layout(Layout::default().h_align(HorizontalAlign::Right)));
+ } else {
+ let s = &format!(
+ ">{:0>3}<{}",
+ elevation_no,
+ " ".repeat(5 * (3 - (no_in_line % 4)))
+ );
+
+ self.wgpu.glyph_brush.queue(Section {
+ screen_position: (self.wgpu.surface_config.width as f32 - 10.0, four_sevenths + line_height * (line_no as f32 + 1.0)),
+ bounds: (self.wgpu.surface_config.width as f32, self.wgpu.surface_config.height as f32),
+ text: vec![Text::new(s)
+ .with_color(rgb_to_srgb(0xdedede, 1.0))
+ .with_scale(self.prefs.fcs)],
+ ..Section::default()
+ }.with_layout(Layout::default().h_align(HorizontalAlign::Right)));
+ }
+ }
+ line_no += 1;
+ }
+ } else {
+ self.wgpu.glyph_brush.queue(Section {
+ screen_position: (self.wgpu.surface_config.width as f32 - 10.0, four_sevenths + line_height),
+ bounds: (self.wgpu.surface_config.width as f32, self.wgpu.surface_config.height as f32),
+ text: vec![Text::new("ELEVATION INFO UNAVAILABLE")
+ .with_color(rgb_to_srgb(0x32cd32, 1.0))
+ .with_scale(self.prefs.fcs)],
+ ..Section::default()
+ }.with_layout(Layout::default().h_align(HorizontalAlign::Right)));
+ }
}
self.wgpu.glyph_brush
diff --git a/nexrad-browser/src/shader.wgsl b/nexrad-browser/src/shader.wgsl
index 83b6747..19b12ad 100644
--- a/nexrad-browser/src/shader.wgsl
+++ b/nexrad-browser/src/shader.wgsl
@@ -18,5 +18,5 @@ fn vs_main(
@fragment
fn fs_main(in: VertexOutput) -> @location(0) vec4 {
- return vec4(0.3, 0.2, 0.1, 1.0);
+ return vec4(0.0, 0.0, 0.0, 1.0);
}
\ No newline at end of file
diff --git a/nexrad2/Cargo.toml b/nexrad2/Cargo.toml
index bf04889..a365f9d 100644
--- a/nexrad2/Cargo.toml
+++ b/nexrad2/Cargo.toml
@@ -10,6 +10,7 @@ edition = "2021"
bzip2-rs = { version = "0.1", optional = true }
bzip2 = { version = "0.4", optional = true }
serde = { version = "1", features = ["derive"], optional = true }
+profiling = "1"
log = "0.4"
diff --git a/nexrad2/src/lib.rs b/nexrad2/src/lib.rs
index deeb043..8377dd9 100644
--- a/nexrad2/src/lib.rs
+++ b/nexrad2/src/lib.rs
@@ -102,7 +102,7 @@ impl Error for NexradParseError {}
pub const NEXRAD2_META_CHUNK_FIXED_LENGTH: usize = 325888;
-
+#[profiling::function]
pub fn parse_nx2_chunk(cursor: &mut (impl Read + Seek)) -> Result {
let mut volume_header = [0u8; 24];
let read = cursor.read(&mut volume_header).map_err(|e| NexradParseError::HeaderReadError(e))?;
diff --git a/nexrad2/src/message31.rs b/nexrad2/src/message31.rs
index 003539e..c57fd67 100644
--- a/nexrad2/src/message31.rs
+++ b/nexrad2/src/message31.rs
@@ -121,6 +121,7 @@ pub struct Message31 {
}
impl DataMoment {
+ #[profiling::function]
pub fn scaled_data(&self) -> Vec {
let mut gates = vec![0u16; self.gdm.data_moment_gate_count as usize];
@@ -152,6 +153,7 @@ impl DataMoment {
}
}
+#[profiling::function]
fn scale_uint(gate: u16, offset: f32, scale: f32) -> f32 {
if scale == 0.0 {
gate as f32
diff --git a/rtwx-desktop/Cargo.toml b/rtwx-desktop/Cargo.toml
new file mode 100644
index 0000000..419efc2
--- /dev/null
+++ b/rtwx-desktop/Cargo.toml
@@ -0,0 +1,12 @@
+[package]
+name = "rtwx-desktop"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+winit = "0.29"
+simple_logger = "4"
+log = "0.4"
+wgpu = "0.18"
\ No newline at end of file
diff --git a/rtwx-desktop/src/main.rs b/rtwx-desktop/src/main.rs
new file mode 100644
index 0000000..0666a9c
--- /dev/null
+++ b/rtwx-desktop/src/main.rs
@@ -0,0 +1,3 @@
+fn main() {
+ simple_logger::init_with_env().unwrap();
+}