From 00baabe3ff462a14d241a86353c285f35f6abcfa Mon Sep 17 00:00:00 2001 From: core Date: Thu, 9 Nov 2023 09:40:59 -0500 Subject: [PATCH] docs and work --- docs/.nojekyll | 0 docs/README.md | 48 ++++++++++++++ docs/index.html | 22 +++++++ nexrad-browser/src/lib.rs | 79 ++++++++++++++++++++--- nexrad-browser/src/rendering/scope.rs | 93 ++++++++++++++++++--------- nexrad-browser/www/index.js | 20 ++++++ nexrad2/src/lib.rs | 2 +- nexrad2/src/message.rs | 2 +- 8 files changed, 222 insertions(+), 44 deletions(-) create mode 100644 docs/.nojekyll create mode 100644 docs/README.md create mode 100644 docs/index.html diff --git a/docs/.nojekyll b/docs/.nojekyll new file mode 100644 index 0000000..e69de29 diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..ad7d3a4 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,48 @@ +# RTWX Operator Manual + +Welcome to the Realtime Weather Explorer (RTWX)! RTWX is a browser-based application allowing you to quickly access live weather radar data from the NEXRAD network, lightning strike data from Blitzortung, weather alerts directly from NWS, and soon predictive weather forecasts from a custom-built predictive weather engine! + +This manual is intended to get scope operators up and running with their RTWX scope as quickly as possible. + +> Currently Implemented Features: +> - Radar scope +> +> Features In Development: +> - Lightning strike data +> +> Planned Features: +> - NWS alerts +> - Predictive Weather Engine + +## Contents + +- [Requirements](#requirements) +- [Opening RTWX](#opening-rtwx) +- [The Scope](#the-scope) +- [Local Operations](#local-operations) +- [Data Server](#data-server) +- [Command Reference](#command-reference) + +## Requirements + +RTWX is a realtime browser application and therefore requires an internet connection. In addition, RTWX requires WebAssembly support (your browser version must be newer than November 2017) and is rendered entirely via WebGL. Your browser **and hardware** must support WebGL 2.0, which is supported by all semi-modern GPUs (integrated GPUs will work) and all browsers released after February 2022. + +## Opening RTWX + +*Reserved* + +## The Scope + +*Reserved* + +## Local Operations + +*Reserved* + +## Data Server + +*Reserved* + +## Command Reference + +*Reserved* \ No newline at end of file diff --git a/docs/index.html b/docs/index.html new file mode 100644 index 0000000..2957354 --- /dev/null +++ b/docs/index.html @@ -0,0 +1,22 @@ + + + + + RTWX Docs + + + + + + +
+ + + + + diff --git a/nexrad-browser/src/lib.rs b/nexrad-browser/src/lib.rs index 13e3f23..e472120 100644 --- a/nexrad-browser/src/lib.rs +++ b/nexrad-browser/src/lib.rs @@ -6,6 +6,8 @@ pub mod vcp; pub mod rendering; +use std::io::Cursor; +use js_sys::Uint8Array; use crate::mode::Mode; use crate::rendering::scope::{Preferences, ScopeState, WgpuState}; @@ -18,11 +20,14 @@ use web_sys::{CanvasRenderingContext2d, HtmlCanvasElement}; use wgpu::SurfaceError; use winit::dpi::PhysicalSize; use winit::event::WindowEvent::KeyboardInput; -use winit::event::{Event, WindowEvent}; +use winit::event::{ElementState, Event, WindowEvent}; use winit::event_loop::{EventLoopBuilder, EventLoopProxy, EventLoopWindowTarget}; +use winit::keyboard::{Key, NamedKey}; use winit::platform::web::{EventLoopExtWebSys, WindowBuilderExtWebSys, WindowExtWebSys}; use winit::window::WindowBuilder; +use nexrad2::Nexrad2Chunk; +use crate::command::{exec, should_newline}; #[wasm_bindgen] extern "C" { @@ -33,6 +38,8 @@ extern "C" { #[derive(Debug, Clone, PartialEq)] pub enum RenderMessage { Resize { new_w: u32, new_h: u32 }, + NewFileUploadedByUser, + NewDataChunkAvailable { chunk: Nexrad2Chunk } } #[wasm_bindgen] @@ -40,7 +47,7 @@ pub struct AbiProxy(EventLoopProxy); #[wasm_bindgen] pub async fn __nxrd_browser_init(w: u32, h: u32) -> AbiProxy { - wasm_logger::init(wasm_logger::Config::new(log::Level::Debug)); + wasm_logger::init(wasm_logger::Config::new(log::Level::Info)); utils::set_panic_hook(); info!("wgpu setup"); @@ -81,6 +88,17 @@ pub async fn __nxrd_browser_init(w: u32, h: u32) -> AbiProxy { RenderMessage::Resize { new_w, new_h } => { scope_state .reconfigure(PhysicalSize::new(new_w, new_h)); + }, + RenderMessage::NewFileUploadedByUser => { + scope_state.new_data_available = true; + }, + RenderMessage::NewDataChunkAvailable { chunk } => { + scope_state.new_data_available = false; + scope_state.scope_mode = Mode::Reflectivity; + scope_state.ar2 = Some(chunk); + scope_state.command_buf_response_mode = true; + scope_state.command_buf = "NEW DATA LOADED SUCCESSFULLY".to_string(); + scope_state.selected_elevation = 1; } }, Event::WindowEvent { @@ -88,7 +106,48 @@ pub async fn __nxrd_browser_init(w: u32, h: u32) -> AbiProxy { window_id: _, } => match event { KeyboardInput { event, .. } => { + if event.state == ElementState::Pressed { + + if event.logical_key == Key::Named(NamedKey::Escape) { + scope_state.command_buf = String::new(); + scope_state.command_buf_response_mode = false; + return; + } + + if scope_state.command_buf_response_mode && (event.logical_key == Key::Named(NamedKey::Backspace) || matches!(event.logical_key, Key::Character(_))) { + scope_state.command_buf = String::new(); + scope_state.command_buf_response_mode = false; + } + + 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); + return; + } + + if event.logical_key == Key::Named(NamedKey::Space) { + if should_newline(&mut scope_state) { + scope_state.command_buf += "\n"; + } else { + scope_state.command_buf += " "; + } + } + + if event.logical_key == Key::Named(NamedKey::Backspace) && scope_state.command_buf.len() != 0 { + scope_state.command_buf = scope_state.command_buf[0..scope_state.command_buf.len()-1].to_string(); + return; + } + + match &event.logical_key { + Key::Character(c) => { + scope_state.command_buf += &c.to_uppercase(); + }, + _ => () + } + } debug!("{:?}", event.physical_key); + } WindowEvent::CloseRequested => { window.exit(); @@ -139,25 +198,25 @@ pub fn render_abi(state: &mut AbiScopeState) { } */ -/* + #[wasm_bindgen] -pub fn load_ar2(buf: &JsValue, scope: &mut AbiScopeState) { +pub fn load_ar2(buf: &JsValue, proxy: &mut AbiProxy) { let array = Uint8Array::new(buf); let rvec = array.to_vec(); let mut cursor = Cursor::new(rvec); let loaded = nexrad2::parse_nx2_chunk(&mut cursor).unwrap(); info!("new data chunk loaded"); - scope.0.ar2 = Some(loaded); - scope.0.new_data_available = false; - scope.0.scope_mode = Reflectivity; - scope.0.command_buf = "NEW DATA LOADED SUCCESSFULLY".to_string(); + + proxy.0.send_event(RenderMessage::NewDataChunkAvailable { chunk: loaded }).unwrap(); } #[wasm_bindgen] -pub fn new_file_available(scope: &mut AbiScopeState) { - scope.0.new_data_available = true; +pub fn new_file_available(proxy: &mut AbiProxy) { + proxy.0.send_event(RenderMessage::NewFileUploadedByUser).unwrap(); } +/* + #[wasm_bindgen] pub fn keydown(state: &mut AbiScopeState, key: String) { if state.0.command_buf_response_mode && (key == "Backspace" || key.len() == 1) { diff --git a/nexrad-browser/src/rendering/scope.rs b/nexrad-browser/src/rendering/scope.rs index e9df051..c790a81 100644 --- a/nexrad-browser/src/rendering/scope.rs +++ b/nexrad-browser/src/rendering/scope.rs @@ -278,40 +278,69 @@ impl ScopeState { let time = Utc::now(); let line_height = self.prefs.fcs - 1.0; - self.wgpu.glyph_brush.queue(Section { - screen_position: (10.0, 8.0), - bounds: (self.wgpu.surface_config.width as f32, self.wgpu.surface_config.height as f32), - text: vec![Text::new(&format!( - "NEXRAD {} {}", - self - .ar2 - .as_ref() - .map(|u| u.volume_header_record.icao.as_str()) - .unwrap_or("INOP"), - zulu(time) - )) - .with_color(rgb_to_srgb(0x32cd32, 1.0)) - .with_scale(self.prefs.fcs)], - ..Section::default() - }); + if self.show_ui { + self.wgpu.glyph_brush.queue(Section { + screen_position: (10.0, 8.0), + bounds: (self.wgpu.surface_config.width as f32, self.wgpu.surface_config.height as f32), + text: vec![Text::new(&format!( + "NEXRAD {} {}", + self + .ar2 + .as_ref() + .map(|u| u.volume_header_record.icao.as_str()) + .unwrap_or("INOP"), + zulu(time) + )) + .with_color(rgb_to_srgb(0x32cd32, 1.0)) + .with_scale(self.prefs.fcs)], + ..Section::default() + }); - self.wgpu.glyph_brush.queue(Section { - screen_position: (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("RADAR INOPERATIVE NO DATA LOADED") - .with_color(rgb_to_srgb(0xff0000, 1.0)) - .with_scale(self.prefs.fcs)], - ..Section::default() - }); + if self.ar2.is_none() { + self.wgpu.glyph_brush.queue(Section { + screen_position: (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("RADAR INOPERATIVE NO DATA LOADED") + .with_color(rgb_to_srgb(0xff0000, 1.0)) + .with_scale(self.prefs.fcs)], + ..Section::default() + }); + } + if self.new_data_available { + self.wgpu.glyph_brush.queue(Section { + screen_position: (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("NEW DATA AVAIL RLD RQD") + .with_color(rgb_to_srgb(0xff0000, 1.0)) + .with_scale(self.prefs.fcs)], + ..Section::default() + }); + } + } - self.wgpu.glyph_brush.queue(Section { - screen_position: (self.wgpu.surface_config.width as f32 - 10.0, 8.0), - bounds: (self.wgpu.surface_config.width as f32, self.wgpu.surface_config.height as f32), - text: vec![Text::new("RADAR SITE") - .with_color(rgb_to_srgb(0x32cd32, 1.0)) - .with_scale(self.prefs.fcs)], - ..Section::default() - }.with_layout(Layout::default().h_align(HorizontalAlign::Right))); + let command_base = (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)), + 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)) + .with_scale(self.prefs.fcs)], + ..Section::default() + }); + } + + if self.show_ui { + self.wgpu.glyph_brush.queue(Section { + screen_position: (self.wgpu.surface_config.width as f32 - 10.0, 8.0), + bounds: (self.wgpu.surface_config.width as f32, self.wgpu.surface_config.height as f32), + text: vec![Text::new("RADAR SITE") + .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 .draw_queued( diff --git a/nexrad-browser/www/index.js b/nexrad-browser/www/index.js index 4b96522..ca1ce06 100644 --- a/nexrad-browser/www/index.js +++ b/nexrad-browser/www/index.js @@ -1,12 +1,32 @@ + + import * as wasm from "./wasm/nexrad_browser.js"; await wasm.default(); let abi_proxy = await wasm.__nxrd_browser_init(window.innerWidth, window.innerHeight); +window.loadar2 = (s) => { + const file = document.getElementById("file").files[0]; + document.getElementById("file").value = null; + const reader = new FileReader(); + reader.addEventListener('load', (event) => { + let data = event.target.result; + wasm.load_ar2(data, abi_proxy); + }); + reader.readAsArrayBuffer(file); + console.log(s); +} + +document.getElementsByTagName("canvas")[0].focus(); + window.onresize = async () => { console.log("window resized!"); await wasm.resize_abi(window.innerWidth, window.innerHeight, abi_proxy); } +document.getElementById("file").onchange = () => { + wasm.new_file_available(abi_proxy); +} + /* function rescaleCanvas(canvas, ctx) { var dpr = window.devicePixelRatio || 1; diff --git a/nexrad2/src/lib.rs b/nexrad2/src/lib.rs index b339cbe..deeb043 100644 --- a/nexrad2/src/lib.rs +++ b/nexrad2/src/lib.rs @@ -18,7 +18,7 @@ pub mod bzip; pub mod message2; pub mod message31; -#[derive(Debug)] +#[derive(Debug, Clone, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Nexrad2Chunk { pub volume_header_record: VolumeHeaderRecord, diff --git a/nexrad2/src/message.rs b/nexrad2/src/message.rs index b755c4d..8a41aec 100644 --- a/nexrad2/src/message.rs +++ b/nexrad2/src/message.rs @@ -32,7 +32,7 @@ pub trait FromBody { fn from_body(body: [u8; BODY_SIZE]) -> Result where Self: Sized; } -#[derive(Debug)] +#[derive(Debug, Clone, PartialEq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum Message { Msg02(Msg02RDAStatusData),