docs and work
This commit is contained in:
parent
e889aa9dc6
commit
00baabe3ff
|
@ -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*
|
|
@ -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>
|
|
@ -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) {
|
||||||
|
|
|
@ -278,40 +278,69 @@ 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;
|
||||||
|
|
||||||
self.wgpu.glyph_brush.queue(Section {
|
if self.show_ui {
|
||||||
screen_position: (10.0, 8.0),
|
self.wgpu.glyph_brush.queue(Section {
|
||||||
bounds: (self.wgpu.surface_config.width as f32, self.wgpu.surface_config.height as f32),
|
screen_position: (10.0, 8.0),
|
||||||
text: vec![Text::new(&format!(
|
bounds: (self.wgpu.surface_config.width as f32, self.wgpu.surface_config.height as f32),
|
||||||
"NEXRAD {} {}",
|
text: vec![Text::new(&format!(
|
||||||
self
|
"NEXRAD {} {}",
|
||||||
.ar2
|
self
|
||||||
.as_ref()
|
.ar2
|
||||||
.map(|u| u.volume_header_record.icao.as_str())
|
.as_ref()
|
||||||
.unwrap_or("INOP"),
|
.map(|u| u.volume_header_record.icao.as_str())
|
||||||
zulu(time)
|
.unwrap_or("INOP"),
|
||||||
))
|
zulu(time)
|
||||||
.with_color(rgb_to_srgb(0x32cd32, 1.0))
|
))
|
||||||
.with_scale(self.prefs.fcs)],
|
.with_color(rgb_to_srgb(0x32cd32, 1.0))
|
||||||
..Section::default()
|
.with_scale(self.prefs.fcs)],
|
||||||
});
|
..Section::default()
|
||||||
|
});
|
||||||
|
|
||||||
self.wgpu.glyph_brush.queue(Section {
|
if self.ar2.is_none() {
|
||||||
screen_position: (10.0, 8.0 + line_height),
|
self.wgpu.glyph_brush.queue(Section {
|
||||||
bounds: (self.wgpu.surface_config.width as f32, self.wgpu.surface_config.height as f32),
|
screen_position: (10.0, 8.0 + line_height),
|
||||||
text: vec![Text::new("RADAR INOPERATIVE NO DATA LOADED")
|
bounds: (self.wgpu.surface_config.width as f32, self.wgpu.surface_config.height as f32),
|
||||||
.with_color(rgb_to_srgb(0xff0000, 1.0))
|
text: vec![Text::new("RADAR INOPERATIVE NO DATA LOADED")
|
||||||
.with_scale(self.prefs.fcs)],
|
.with_color(rgb_to_srgb(0xff0000, 1.0))
|
||||||
..Section::default()
|
.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 {
|
let command_base = (self.wgpu.surface_config.height / 3) as f32;
|
||||||
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),
|
for (line_no, line) in self.command_buf.split('\n').enumerate() {
|
||||||
text: vec![Text::new("RADAR SITE")
|
self.wgpu.glyph_brush.queue(Section {
|
||||||
.with_color(rgb_to_srgb(0x32cd32, 1.0))
|
screen_position: (10.0, command_base + (line_height * line_no as f32)),
|
||||||
.with_scale(self.prefs.fcs)],
|
bounds: (self.wgpu.surface_config.width as f32, self.wgpu.surface_config.height as f32),
|
||||||
..Section::default()
|
text: vec![Text::new(line)
|
||||||
}.with_layout(Layout::default().h_align(HorizontalAlign::Right)));
|
.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
|
self.wgpu.glyph_brush
|
||||||
.draw_queued(
|
.draw_queued(
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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),
|
||||||
|
|
Loading…
Reference in New Issue