docs and work

This commit is contained in:
core 2023-11-09 09:40:59 -05:00
parent e889aa9dc6
commit 00baabe3ff
Signed by: core
GPG Key ID: FDBF740DADDCEECF
8 changed files with 222 additions and 44 deletions

0
docs/.nojekyll Normal file
View File

48
docs/README.md Normal file
View File

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

22
docs/index.html Normal file
View File

@ -0,0 +1,22 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>RTWX Docs</title>
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<meta name="description" content="Description">
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0">
<link rel="stylesheet" href="//cdn.jsdelivr.net/npm/docsify@4/lib/themes/vue.css">
</head>
<body>
<div id="app"></div>
<script>
window.$docsify = {
name: 'RTWX',
repo: ''
}
</script>
<!-- Docsify v4 -->
<script src="//cdn.jsdelivr.net/npm/docsify@4"></script>
</body>
</html>

View File

@ -6,6 +6,8 @@ pub mod vcp;
pub mod rendering; pub mod rendering;
use std::io::Cursor;
use js_sys::Uint8Array;
use crate::mode::Mode; use crate::mode::Mode;
use crate::rendering::scope::{Preferences, ScopeState, WgpuState}; use crate::rendering::scope::{Preferences, ScopeState, WgpuState};
@ -18,11 +20,14 @@ use web_sys::{CanvasRenderingContext2d, HtmlCanvasElement};
use wgpu::SurfaceError; use wgpu::SurfaceError;
use winit::dpi::PhysicalSize; use winit::dpi::PhysicalSize;
use winit::event::WindowEvent::KeyboardInput; 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::event_loop::{EventLoopBuilder, EventLoopProxy, EventLoopWindowTarget};
use winit::keyboard::{Key, NamedKey};
use winit::platform::web::{EventLoopExtWebSys, WindowBuilderExtWebSys, WindowExtWebSys}; use winit::platform::web::{EventLoopExtWebSys, WindowBuilderExtWebSys, WindowExtWebSys};
use winit::window::WindowBuilder; use winit::window::WindowBuilder;
use nexrad2::Nexrad2Chunk;
use crate::command::{exec, should_newline};
#[wasm_bindgen] #[wasm_bindgen]
extern "C" { extern "C" {
@ -33,6 +38,8 @@ extern "C" {
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub enum RenderMessage { pub enum RenderMessage {
Resize { new_w: u32, new_h: u32 }, Resize { new_w: u32, new_h: u32 },
NewFileUploadedByUser,
NewDataChunkAvailable { chunk: Nexrad2Chunk }
} }
#[wasm_bindgen] #[wasm_bindgen]
@ -40,7 +47,7 @@ pub struct AbiProxy(EventLoopProxy<RenderMessage>);
#[wasm_bindgen] #[wasm_bindgen]
pub async fn __nxrd_browser_init(w: u32, h: u32) -> AbiProxy { 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(); utils::set_panic_hook();
info!("wgpu setup"); info!("wgpu setup");
@ -81,6 +88,17 @@ pub async fn __nxrd_browser_init(w: u32, h: u32) -> AbiProxy {
RenderMessage::Resize { new_w, new_h } => { RenderMessage::Resize { new_w, new_h } => {
scope_state scope_state
.reconfigure(PhysicalSize::new(new_w, new_h)); .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 { Event::WindowEvent {
@ -88,7 +106,48 @@ pub async fn __nxrd_browser_init(w: u32, h: u32) -> AbiProxy {
window_id: _, window_id: _,
} => match event { } => match event {
KeyboardInput { 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); debug!("{:?}", event.physical_key);
} }
WindowEvent::CloseRequested => { WindowEvent::CloseRequested => {
window.exit(); window.exit();
@ -139,25 +198,25 @@ pub fn render_abi(state: &mut AbiScopeState) {
} }
*/ */
/*
#[wasm_bindgen] #[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 array = Uint8Array::new(buf);
let rvec = array.to_vec(); let rvec = array.to_vec();
let mut cursor = Cursor::new(rvec); let mut cursor = Cursor::new(rvec);
let loaded = nexrad2::parse_nx2_chunk(&mut cursor).unwrap(); let loaded = nexrad2::parse_nx2_chunk(&mut cursor).unwrap();
info!("new data chunk loaded"); info!("new data chunk loaded");
scope.0.ar2 = Some(loaded);
scope.0.new_data_available = false; proxy.0.send_event(RenderMessage::NewDataChunkAvailable { chunk: loaded }).unwrap();
scope.0.scope_mode = Reflectivity;
scope.0.command_buf = "NEW DATA LOADED SUCCESSFULLY".to_string();
} }
#[wasm_bindgen] #[wasm_bindgen]
pub fn new_file_available(scope: &mut AbiScopeState) { pub fn new_file_available(proxy: &mut AbiProxy) {
scope.0.new_data_available = true; proxy.0.send_event(RenderMessage::NewFileUploadedByUser).unwrap();
} }
/*
#[wasm_bindgen] #[wasm_bindgen]
pub fn keydown(state: &mut AbiScopeState, key: String) { pub fn keydown(state: &mut AbiScopeState, key: String) {
if state.0.command_buf_response_mode && (key == "Backspace" || key.len() == 1) { if state.0.command_buf_response_mode && (key == "Backspace" || key.len() == 1) {

View File

@ -278,6 +278,7 @@ impl ScopeState {
let time = Utc::now(); let time = Utc::now();
let line_height = self.prefs.fcs - 1.0; let line_height = self.prefs.fcs - 1.0;
if self.show_ui {
self.wgpu.glyph_brush.queue(Section { self.wgpu.glyph_brush.queue(Section {
screen_position: (10.0, 8.0), screen_position: (10.0, 8.0),
bounds: (self.wgpu.surface_config.width as f32, self.wgpu.surface_config.height as f32), bounds: (self.wgpu.surface_config.width as f32, self.wgpu.surface_config.height as f32),
@ -295,6 +296,7 @@ impl ScopeState {
..Section::default() ..Section::default()
}); });
if self.ar2.is_none() {
self.wgpu.glyph_brush.queue(Section { self.wgpu.glyph_brush.queue(Section {
screen_position: (10.0, 8.0 + line_height), screen_position: (10.0, 8.0 + line_height),
bounds: (self.wgpu.surface_config.width as f32, self.wgpu.surface_config.height as f32), bounds: (self.wgpu.surface_config.width as f32, self.wgpu.surface_config.height as f32),
@ -303,7 +305,33 @@ impl ScopeState {
.with_scale(self.prefs.fcs)], .with_scale(self.prefs.fcs)],
..Section::default() ..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()
});
}
}
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 { self.wgpu.glyph_brush.queue(Section {
screen_position: (self.wgpu.surface_config.width as f32 - 10.0, 8.0), 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), bounds: (self.wgpu.surface_config.width as f32, self.wgpu.surface_config.height as f32),
@ -312,6 +340,7 @@ impl ScopeState {
.with_scale(self.prefs.fcs)], .with_scale(self.prefs.fcs)],
..Section::default() ..Section::default()
}.with_layout(Layout::default().h_align(HorizontalAlign::Right))); }.with_layout(Layout::default().h_align(HorizontalAlign::Right)));
}
self.wgpu.glyph_brush self.wgpu.glyph_brush
.draw_queued( .draw_queued(

View File

@ -1,12 +1,32 @@
import * as wasm from "./wasm/nexrad_browser.js"; import * as wasm from "./wasm/nexrad_browser.js";
await wasm.default(); await wasm.default();
let abi_proxy = await wasm.__nxrd_browser_init(window.innerWidth, window.innerHeight); 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 () => { window.onresize = async () => {
console.log("window resized!"); console.log("window resized!");
await wasm.resize_abi(window.innerWidth, window.innerHeight, abi_proxy); await wasm.resize_abi(window.innerWidth, window.innerHeight, abi_proxy);
} }
document.getElementById("file").onchange = () => {
wasm.new_file_available(abi_proxy);
}
/* /*
function rescaleCanvas(canvas, ctx) { function rescaleCanvas(canvas, ctx) {
var dpr = window.devicePixelRatio || 1; var dpr = window.devicePixelRatio || 1;

View File

@ -18,7 +18,7 @@ pub mod bzip;
pub mod message2; pub mod message2;
pub mod message31; pub mod message31;
#[derive(Debug)] #[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Nexrad2Chunk { pub struct Nexrad2Chunk {
pub volume_header_record: VolumeHeaderRecord, pub volume_header_record: VolumeHeaderRecord,

View File

@ -32,7 +32,7 @@ pub trait FromBody<const BODY_SIZE: usize> {
fn from_body(body: [u8; BODY_SIZE]) -> Result<Self, NexradParseError> where Self: Sized; fn from_body(body: [u8; BODY_SIZE]) -> Result<Self, NexradParseError> where Self: Sized;
} }
#[derive(Debug)] #[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Message { pub enum Message {
Msg02(Msg02RDAStatusData), Msg02(Msg02RDAStatusData),