diff --git a/TCLT20231105_090243_V08 b/TCLT20231105_090243_V08 new file mode 100644 index 0000000..855d362 Binary files /dev/null and b/TCLT20231105_090243_V08 differ diff --git a/nexrad-browser/Cargo.toml b/nexrad-browser/Cargo.toml index d4c8d7f..03d0cf6 100644 --- a/nexrad-browser/Cargo.toml +++ b/nexrad-browser/Cargo.toml @@ -14,20 +14,32 @@ crate-type = ["cdylib", "rlib"] default = ["console_error_panic_hook"] [dependencies] -wasm-bindgen = "0.2.84" - -# The `console_error_panic_hook` crate provides better debugging of panics by -# logging them with `console.error`. This is great for development, but requires -# all the `std::fmt` and `std::panicking` infrastructure, so isn't great for -# code size when deploying. -console_error_panic_hook = { version = "0.1.7", optional = true } +wasm-bindgen = "0.2" +console_error_panic_hook = { version = "0.1", optional = true } nexrad2 = { version = "0.1.0", path = "../nexrad2", default-features = false, features = ["bzip-impl-bzip2-rs", "serde_derive"] } log = "0.4" -web-sys = "0.3" -js-sys = "0.3" wasm-logger = "0.2" serde-wasm-bindgen = "0.6" +chrono = "0.4" + +[dependencies.js-sys] +version = "0.3" + +[dependencies.web-sys] +version = "0.3" +features = [ + 'CanvasRenderingContext2d', + 'Document', + 'Element', + 'HtmlCanvasElement', + 'HtmlInputElement', + 'Window', + 'FileList', + 'File', + 'FileReader', + 'EventTarget' +] [dev-dependencies] -wasm-bindgen-test = "0.3.34" +wasm-bindgen-test = "0.3" diff --git a/nexrad-browser/src/command.rs b/nexrad-browser/src/command.rs new file mode 100644 index 0000000..e915614 --- /dev/null +++ b/nexrad-browser/src/command.rs @@ -0,0 +1,22 @@ +use web_sys::FileReader; +use crate::loadar2; +use crate::rendering::render; +use crate::scope::ScopeState; + +pub fn should_newline(state: &mut ScopeState) -> bool { + state.command_buf.starts_with("SET MODE") +} + +pub fn exec(state: &mut ScopeState, command: String) { + if command == "CLF OV" { + state.file_input.click(); + } else if command == "CLF RELOAD" { + state.command_buf = "SYSTEM PROCESSING".to_string(); + state.command_buf_response_mode = true; + render(state).expect("rerender failed"); + loadar2("file"); + } else { + state.command_buf = "UNRECOGNIZED COMMAND".to_string(); + state.command_buf_response_mode = true; + } +} \ No newline at end of file diff --git a/nexrad-browser/src/equirectangular.rs b/nexrad-browser/src/equirectangular.rs new file mode 100644 index 0000000..d3da646 --- /dev/null +++ b/nexrad-browser/src/equirectangular.rs @@ -0,0 +1,7 @@ +pub const EARTH_RADIUS_MILES: f64 = 3958.8; + +pub fn latlong_to_xy(lat: f64, long: f64) -> (f64, f64) { + // x = r λ cos(φ0) + // y = r φ + (EARTH_RADIUS_MILES * long * lat.cos(), EARTH_RADIUS_MILES * lat) +} \ No newline at end of file diff --git a/nexrad-browser/src/lib.rs b/nexrad-browser/src/lib.rs index 4c71a41..05dea0f 100644 --- a/nexrad-browser/src/lib.rs +++ b/nexrad-browser/src/lib.rs @@ -1,36 +1,114 @@ pub mod utils; +pub mod rendering; +pub mod mode; +pub mod scope; +pub mod equirectangular; +pub mod sites; +pub mod command; +pub mod vcp; use std::io::Cursor; +use std::ops::{Deref, DerefMut}; use js_sys::Uint8Array; use log::info; use wasm_bindgen::prelude::*; -use nexrad2::Nexrad2Chunk; +use web_sys::{CanvasRenderingContext2d, HtmlCanvasElement}; +use crate::command::{exec, should_newline}; +use crate::mode::Mode; +use crate::scope::{Preferences, RenderState, ScopeState}; #[wasm_bindgen] extern "C" { - fn alert(s: &str); + fn rescaleCanvas(canvas: &HtmlCanvasElement, ctx: &CanvasRenderingContext2d); + fn loadar2(c: &str); } #[wasm_bindgen] -pub fn __nxrd_browser_init() { +pub struct AbiScopeState(ScopeState); + +#[wasm_bindgen] +pub fn __nxrd_browser_init() -> AbiScopeState { wasm_logger::init(wasm_logger::Config::new(log::Level::Debug)); utils::set_panic_hook(); + + info!("initializing the scope"); + + info!("finding canvas element"); + + let document = web_sys::window().expect("window should exist").document().expect("document should exist"); + let canvas = document.get_element_by_id("canvas").expect("canvas element should exist"); + + let canvas: web_sys::HtmlCanvasElement = canvas.dyn_into::().map_err(|_| ()).expect("canvas is not a canvas"); + let context = canvas.get_context("2d").expect("failed to get canvas rendering context").expect("canvas rendering context doesn't exist").dyn_into::().map_err(|_| ()).expect("2d context is not a 2d context"); + + info!("finding form input element"); + + let file = document.get_element_by_id("file").expect("file element should exist"); + let file: web_sys::HtmlInputElement = file.dyn_into::().map_err(|_| ()).expect("file input is not an input"); + + let scope_state = ScopeState { + ar2: None, + scope_mode: Mode::RadarInoperative, + render_state: RenderState { + canvas, + context + }, + file_input: file, + lat: 30.48500, // jacksonville + long: -81.70200, // jacksonville + prefs: Preferences { + fcs: 20 + }, + command_buf: String::new(), + command_buf_response_mode: false, + new_data_available: false + }; + info!("nexrad-browser initialized successfully"); + + AbiScopeState(scope_state) } #[wasm_bindgen] -pub fn load_ar2(buf: &JsValue) -> JsValue { - - info!("loading archive 2 file"); - info!("load 01: convert to uint8array"); +pub fn render_abi(state: &mut AbiScopeState) { + rendering::render(&mut state.0).unwrap() +} + +#[wasm_bindgen] +pub fn load_ar2(buf: &JsValue, scope: &mut AbiScopeState) { let array = Uint8Array::new(buf); - info!("load 02: convert to rust vec"); let rvec = array.to_vec(); - info!("load 03: create cursor"); let mut cursor = Cursor::new(rvec); - info!("load 04: load"); let loaded = nexrad2::parse_nx2_chunk(&mut cursor).unwrap(); - info!("load 05: dump"); - info!("Loaded: {:#?}", loaded); - serde_wasm_bindgen::to_value(&loaded).unwrap() + info!("new data chunk loaded"); + scope.0.ar2 = Some(loaded); + scope.0.new_data_available = false; +} + +#[wasm_bindgen] +pub fn new_file_available(scope: &mut AbiScopeState) { + scope.0.new_data_available = true; +} + +#[wasm_bindgen] +pub fn keydown(state: &mut AbiScopeState, key: String) { + if state.0.command_buf_response_mode { + state.0.command_buf = String::new(); + state.0.command_buf_response_mode = false; + } + + if key == "Escape" { + state.0.command_buf = String::new(); + } else if key == "Enter" { + let cmd = state.0.command_buf.clone(); + exec(&mut state.0, cmd.replace('\n', " ")); + state.0.command_buf = String::new(); + } else if key == " " && should_newline(&mut state.0) { + state.0.command_buf += "\n"; + } else if key == "Backspace" { + if state.0.command_buf.is_empty() { return; } + state.0.command_buf = state.0.command_buf[0..state.0.command_buf.len()-1].to_string(); + } else if key.len() == 1 { + state.0.command_buf += &key.to_uppercase(); + } } \ No newline at end of file diff --git a/nexrad-browser/src/mode.rs b/nexrad-browser/src/mode.rs new file mode 100644 index 0000000..18dd4bc --- /dev/null +++ b/nexrad-browser/src/mode.rs @@ -0,0 +1,19 @@ +#[derive(Debug)] +pub enum Mode { + /// REF + Reflectivity, + /// VEL + Velocity, + /// SW + SpectrumWidth, + /// ZDR + DifferentialReflectivity, + /// PHI + DifferentialPhase, + /// RHO + CorrelationCoefficient, + /// CFP + ClutterFilterPowerRemoved, + /// RADR INOP + RadarInoperative +} \ No newline at end of file diff --git a/nexrad-browser/src/rendering.rs b/nexrad-browser/src/rendering.rs new file mode 100644 index 0000000..7fa493f --- /dev/null +++ b/nexrad-browser/src/rendering.rs @@ -0,0 +1,80 @@ +use chrono::{DateTime, Timelike, Utc}; +use wasm_bindgen::JsValue; +use crate::rescaleCanvas; +use crate::scope::ScopeState; +use crate::utils::parse_date; +use crate::vcp::vcp_string; + +pub const TEXT_COLOR_RED: &str = "#ef0000"; +pub const TEXT_COLOR_GREEN: &str = "#4af626"; +pub const TEXT_COLOR_WHITE: &str = "#dedede"; + +pub fn time(h: usize, m: usize, s: usize, tag_end: &str) -> String { + format!("{:0>2}:{:0>2}:{:0>2}{}", h, m, s, tag_end) +} +pub fn hms(t: DateTime) -> (usize, usize, usize) { + (t.hour() as usize, t.minute() as usize, t.second() as usize) +} +pub fn zulu(t: DateTime) -> String { + let t2 = hms(t); + time(t2.0, t2.1, t2.2, "Z") +} + +pub fn render(state: &mut ScopeState) -> Result<(), JsValue> { + // DPI rescaling + rescaleCanvas(&state.render_state.canvas, &state.render_state.context); + + let ctx = &state.render_state.context; + let canvas = &state.render_state.canvas; + + ctx.set_font(&format!("{}px monospace", state.prefs.fcs)); + + // clear canvas + ctx.reset_transform()?; + ctx.clear_rect(0.0, 0.0, canvas.width() as f64, canvas.height() as f64); + + // render out the background + ctx.set_fill_style(&JsValue::from_str("black")); + ctx.fill_rect(0.0, 0.0, canvas.width() as f64, canvas.height() as f64); + + let time = Utc::now(); + + ctx.set_fill_style(&JsValue::from_str(TEXT_COLOR_GREEN)); + + // render out the scope id line + ctx.fill_text(&format!("NEXRAD {} {}", state.ar2.as_ref().map(|u| u.volume_header_record.icao.as_str()).unwrap_or("INOP"), zulu(time)), 50.0, 50.0)?; + + if state.ar2.is_none() { + // inop alert + ctx.set_fill_style(&JsValue::from_str(TEXT_COLOR_RED)); + ctx.fill_text("RADAR INOPERATIVE NO DATA LOADED", 50.0, 50.0 + (state.prefs.fcs as f64))?; + ctx.set_fill_style(&JsValue::from_str(TEXT_COLOR_GREEN)); + } + if state.new_data_available { + // inop alert + ctx.set_fill_style(&JsValue::from_str(TEXT_COLOR_RED)); + ctx.fill_text("NEW DATA AVAIL RLD RQD", 50.0, 50.0 + (state.prefs.fcs as f64) * 2.0)?; + ctx.set_fill_style(&JsValue::from_str(TEXT_COLOR_GREEN)); + } + + // render the command buffer + for (line_no, line) in state.command_buf.split('\n').enumerate() { + ctx.fill_text(line, 50.0, (canvas.height() / 3) as f64 + (state.prefs.fcs * line_no) as f64)?; + } + + // render the radar site info + ctx.set_text_align("right"); + ctx.fill_text("RADAR SITE", (canvas.width() - 50) as f64, 50.0)?; + if let Some(ar2) = &state.ar2 { + ctx.fill_text(&format!("{} VCP {} {}", ar2.volume_header_record.icao, ar2.meta_rda_status_data.vcp, vcp_string(ar2.meta_rda_status_data.vcp)), (canvas.width() - 50) as f64, 50.0 + (state.prefs.fcs as f64))?; + let recorded = parse_date(ar2.volume_header_record.date, ar2.volume_header_record.time); + let delay = time - recorded; + ctx.fill_text(&format!("SITE DELAY {}:{}:{}", delay.num_hours(), delay.num_minutes() - (delay.num_hours() * 60), delay.num_seconds() - (delay.num_minutes() - (delay.num_hours() * 60) * 60)), (canvas.width() - 50) as f64, 50.0 + (state.prefs.fcs as f64 * 2.0))?; + } else { + ctx.fill_text("SITE INFORMATION UNAVAILABLE", (canvas.width() - 50) as f64, 50.0 + state.prefs.fcs as f64)?; + ctx.fill_text("DELAY INFORMATION UNAVAILABLE", (canvas.width() - 50) as f64, 50.0 + (state.prefs.fcs * 2) as f64)?; + } + + + Ok(()) +} \ No newline at end of file diff --git a/nexrad-browser/src/scope.rs b/nexrad-browser/src/scope.rs new file mode 100644 index 0000000..cce09a4 --- /dev/null +++ b/nexrad-browser/src/scope.rs @@ -0,0 +1,25 @@ +use web_sys::{CanvasRenderingContext2d, HtmlCanvasElement, HtmlInputElement}; +use nexrad2::Nexrad2Chunk; +use crate::mode::Mode; + +pub struct ScopeState { + pub ar2: Option, + pub scope_mode: Mode, + pub render_state: RenderState, + pub file_input: HtmlInputElement, + pub lat: f64, + pub long: f64, + pub prefs: Preferences, + pub command_buf: String, + pub command_buf_response_mode: bool, + pub new_data_available: bool +} + +pub struct RenderState { + pub canvas: HtmlCanvasElement, + pub context: CanvasRenderingContext2d +} + +pub struct Preferences { + pub fcs: usize +} \ No newline at end of file diff --git a/nexrad-browser/src/sites.rs b/nexrad-browser/src/sites.rs new file mode 100644 index 0000000..85cf9aa --- /dev/null +++ b/nexrad-browser/src/sites.rs @@ -0,0 +1,175 @@ +use std::cell::OnceCell; +use std::collections::HashMap; + +pub struct Site { + pub lat: f64, + pub long: f64, + pub country: String, + pub state: String, + pub place: String +} + +pub fn sites() -> HashMap { + HashMap::from([ + ("RODN".to_string(), Site { lat: 26.307796, long: 127.903422, country: "JPN".to_string(), state: "OKI".to_string(), place: "Kadena Air Base".to_string() }), + ("RKSG".to_string(), Site { lat: 37.207652, long: 127.285614, country: "KOR".to_string(), state: "41".to_string(), place: "Camp Humphreys".to_string() }), + ("RKJK".to_string(), Site { lat: 35.92417, long: 126.62222, country: "KOR".to_string(), state: "45".to_string(), place: "Kunsan Air Base".to_string() }), + ("PABC".to_string(), Site { lat: 60.791987, long: -161.876539, country: "USA".to_string(), state: "AK".to_string(), place: "Bethel".to_string() }), + ("PAPD".to_string(), Site { lat: 65.0351238, long: -147.5014222, country: "USA".to_string(), state: "AK".to_string(), place: "Fairbanks/Pedro Dome".to_string() }), + ("PAHG".to_string(), Site { lat: 60.6156335, long: -151.2832296, country: "USA".to_string(), state: "AK".to_string(), place: "Kenai".to_string() }), + ("PAKC".to_string(), Site { lat: 58.6794558, long: -156.6293335, country: "USA".to_string(), state: "AK".to_string(), place: "King Salmon".to_string() }), + ("PAIH".to_string(), Site { lat: 59.46194, long: -146.30111, country: "USA".to_string(), state: "AK".to_string(), place: "Middleton Island".to_string() }), + ("PAEC".to_string(), Site { lat: 64.5114973, long: -165.2949071, country: "USA".to_string(), state: "AK".to_string(), place: "Nome".to_string() }), + ("PACG".to_string(), Site { lat: 56.85214, long: -135.552417, country: "USA".to_string(), state: "AK".to_string(), place: "Sitka/Biorka Island".to_string() }), + ("KBMX".to_string(), Site { lat: 33.1722806, long: -86.7698425, country: "USA".to_string(), state: "AL".to_string(), place: "Birmingham".to_string() }), + ("KEOX".to_string(), Site { lat: 31.4605622, long: -85.4592401, country: "USA".to_string(), state: "AL".to_string(), place: "Fort Rucker".to_string() }), + ("KHTX".to_string(), Site { lat: 34.930508, long: -86.0837388, country: "USA".to_string(), state: "AL".to_string(), place: "Huntsville".to_string() }), + ("KMXX".to_string(), Site { lat: 32.5366608, long: -85.7897848, country: "USA".to_string(), state: "AL".to_string(), place: "Maxwell AFB".to_string() }), + ("KMOB".to_string(), Site { lat: 30.6795378, long: -88.2397816, country: "USA".to_string(), state: "AL".to_string(), place: "Mobile".to_string() }), + ("KSRX".to_string(), Site { lat: 35.2904423, long: -94.3619075, country: "USA".to_string(), state: "AR".to_string(), place: "Fort Smith".to_string() }), + ("KLZK".to_string(), Site { lat: 34.8365261, long: -92.2621697, country: "USA".to_string(), state: "AR".to_string(), place: "Little Rock".to_string() }), + ("KFSX".to_string(), Site { lat: 34.574449, long: -111.198367, country: "USA".to_string(), state: "AZ".to_string(), place: "Flagstaff".to_string() }), + ("KIWA".to_string(), Site { lat: 33.289111, long: -111.6700092, country: "USA".to_string(), state: "AZ".to_string(), place: "Phoenix".to_string() }), + ("KEMX".to_string(), Site { lat: 31.8937186, long: -110.6304306, country: "USA".to_string(), state: "AZ".to_string(), place: "Tucson".to_string() }), + ("KYUX".to_string(), Site { lat: 32.4953477, long: -114.6567214, country: "USA".to_string(), state: "AZ".to_string(), place: "Yuma".to_string() }), + ("KBBX".to_string(), Site { lat: 39.4956958, long: -121.6316557, country: "USA".to_string(), state: "CA".to_string(), place: "Beale AFB".to_string() }), + ("KEYX".to_string(), Site { lat: 35.0979358, long: -117.5608832, country: "USA".to_string(), state: "CA".to_string(), place: "Edwards AFB".to_string() }), + ("KBHX".to_string(), Site { lat: 40.4986955, long: -124.2918867, country: "USA".to_string(), state: "CA".to_string(), place: "Eureka".to_string() }), + ("KVTX".to_string(), Site { lat: 34.4116386, long: -119.1795641, country: "USA".to_string(), state: "CA".to_string(), place: "Los Angeles".to_string() }), + ("KDAX".to_string(), Site { lat: 38.5011529, long: -121.6778487, country: "USA".to_string(), state: "CA".to_string(), place: "Sacramento".to_string() }), + ("KNKX".to_string(), Site { lat: 32.9189891, long: -117.041814, country: "USA".to_string(), state: "CA".to_string(), place: "San Diego".to_string() }), + ("KMUX".to_string(), Site { lat: 37.155152, long: -121.8984577, country: "USA".to_string(), state: "CA".to_string(), place: "San Francisco".to_string() }), + ("KHNX".to_string(), Site { lat: 36.3142088, long: -119.6320903, country: "USA".to_string(), state: "CA".to_string(), place: "San Joaquin Valley".to_string() }), + ("KSOX".to_string(), Site { lat: 33.8176452, long: -117.6359743, country: "USA".to_string(), state: "CA".to_string(), place: "Santa Ana Mountains".to_string() }), + ("KVBG".to_string(), Site { lat: 34.8383137, long: -120.3977805, country: "USA".to_string(), state: "CA".to_string(), place: "Vandenberg AFB".to_string() }), + ("KFTG".to_string(), Site { lat: 39.7866156, long: -104.5458126, country: "USA".to_string(), state: "CO".to_string(), place: "Denver".to_string() }), + ("KGJX".to_string(), Site { lat: 39.0619824, long: -108.2137012, country: "USA".to_string(), state: "CO".to_string(), place: "Grand Junction".to_string() }), + ("KPUX".to_string(), Site { lat: 38.4595034, long: -104.1816223, country: "USA".to_string(), state: "CO".to_string(), place: "Pueblo".to_string() }), + ("KDOX".to_string(), Site { lat: 38.8257651, long: -75.4400763, country: "USA".to_string(), state: "DE".to_string(), place: "Dover AFB".to_string() }), + ("KEVX".to_string(), Site { lat: 30.5649908, long: -85.921559, country: "USA".to_string(), state: "FL".to_string(), place: "Eglin AFB".to_string() }), + ("KJAX".to_string(), Site { lat: 30.4846878, long: -81.7018917, country: "USA".to_string(), state: "FL".to_string(), place: "Jacksonville".to_string() }), + ("KBYX".to_string(), Site { lat: 24.5974996, long: -81.7032355, country: "USA".to_string(), state: "FL".to_string(), place: "Key West".to_string() }), + ("KMLB".to_string(), Site { lat: 28.1131808, long: -80.6540988, country: "USA".to_string(), state: "FL".to_string(), place: "Melbourne".to_string() }), + ("KAMX".to_string(), Site { lat: 25.6111275, long: -80.412747, country: "USA".to_string(), state: "FL".to_string(), place: "Miami".to_string() }), + ("KTLH".to_string(), Site { lat: 30.397568, long: -84.3289116, country: "USA".to_string(), state: "FL".to_string(), place: "Tallahassee".to_string() }), + ("KTBW".to_string(), Site { lat: 27.7054701, long: -82.40179, country: "USA".to_string(), state: "FL".to_string(), place: "Tampa".to_string() }), + ("KFFC".to_string(), Site { lat: 33.3635771, long: -84.565866, country: "USA".to_string(), state: "GA".to_string(), place: "Atlanta".to_string() }), + ("KVAX".to_string(), Site { lat: 30.8903853, long: -83.0019021, country: "USA".to_string(), state: "GA".to_string(), place: "Moody AFB".to_string() }), + ("KJGX".to_string(), Site { lat: 32.6755239, long: -83.3508575, country: "USA".to_string(), state: "GA".to_string(), place: "Robins AFB".to_string() }), + ("PGUA".to_string(), Site { lat: 13.455965, long: 144.8111022, country: "USA".to_string(), state: "GU".to_string(), place: "Andersen AFB".to_string() }), + ("PHKI".to_string(), Site { lat: 21.8938762, long: -159.5524585, country: "USA".to_string(), state: "HI".to_string(), place: "Kauai".to_string() }), + ("PHKM".to_string(), Site { lat: 20.1254606, long: -155.778054, country: "USA".to_string(), state: "HI".to_string(), place: "Kohala".to_string() }), + ("PHMO".to_string(), Site { lat: 21.1327531, long: -157.1802807, country: "USA".to_string(), state: "HI".to_string(), place: "Molokai".to_string() }), + ("PHWA".to_string(), Site { lat: 19.0950155, long: -155.5688846, country: "USA".to_string(), state: "HI".to_string(), place: "South Shore".to_string() }), + ("KDVN".to_string(), Site { lat: 41.611556, long: -90.5809987, country: "USA".to_string(), state: "IA".to_string(), place: "Davenport".to_string() }), + ("KDMX".to_string(), Site { lat: 41.7311788, long: -93.7229235, country: "USA".to_string(), state: "IA".to_string(), place: "Des Moines".to_string() }), + ("KCBX".to_string(), Site { lat: 43.4902104, long: -116.2360436, country: "USA".to_string(), state: "ID".to_string(), place: "Boise".to_string() }), + ("KSFX".to_string(), Site { lat: 43.1055967, long: -112.6860487, country: "USA".to_string(), state: "ID".to_string(), place: "Pocatello/Idaho Falls".to_string() }), + ("KLOT".to_string(), Site { lat: 41.6044264, long: -88.084361, country: "USA".to_string(), state: "IL".to_string(), place: "Chicago".to_string() }), + ("KILX".to_string(), Site { lat: 40.150544, long: -89.336842, country: "USA".to_string(), state: "IL".to_string(), place: "Lincoln".to_string() }), + ("KIND".to_string(), Site { lat: 39.7074962, long: -86.2803675, country: "USA".to_string(), state: "IN".to_string(), place: "Indianapolis".to_string() }), + ("KIWX".to_string(), Site { lat: 41.3586356, long: -85.7000488, country: "USA".to_string(), state: "IN".to_string(), place: "North Webster".to_string() }), + ("KVWX".to_string(), Site { lat: 38.2603901, long: -87.7246553, country: "USA".to_string(), state: "IN".to_string(), place: "Owensville (Evansville)".to_string() }), + ("KDDC".to_string(), Site { lat: 37.7608043, long: -99.9688053, country: "USA".to_string(), state: "KS".to_string(), place: "Dodge City".to_string() }), + ("KGLD".to_string(), Site { lat: 39.3667737, long: -101.7004341, country: "USA".to_string(), state: "KS".to_string(), place: "Goodland".to_string() }), + ("KTWX".to_string(), Site { lat: 38.996998, long: -96.232618, country: "USA".to_string(), state: "KS".to_string(), place: "Topeka".to_string() }), + ("KICT".to_string(), Site { lat: 37.6545724, long: -97.4431461, country: "USA".to_string(), state: "KS".to_string(), place: "Wichita".to_string() }), + ("KHPX".to_string(), Site { lat: 36.7368894, long: -87.2854328, country: "USA".to_string(), state: "KY".to_string(), place: "Fort Campbell".to_string() }), + ("KJKL".to_string(), Site { lat: 37.590762, long: -83.313039, country: "USA".to_string(), state: "KY".to_string(), place: "Jackson".to_string() }), + ("KLVX".to_string(), Site { lat: 37.9753058, long: -85.9438455, country: "USA".to_string(), state: "KY".to_string(), place: "Louisville".to_string() }), + ("KPAH".to_string(), Site { lat: 37.0683618, long: -88.7720257, country: "USA".to_string(), state: "KY".to_string(), place: "Paducah".to_string() }), + ("KPOE".to_string(), Site { lat: 31.1556923, long: -92.9762596, country: "USA".to_string(), state: "LA".to_string(), place: "Fort Polk".to_string() }), + ("KHDC".to_string(), Site { lat: 30.521667, long: -90.418333, country: "USA".to_string(), state: "LA".to_string(), place: "New Orleans (Hammond)".to_string() }), + ("KLCH".to_string(), Site { lat: 30.125382, long: -93.2161188, country: "USA".to_string(), state: "LA".to_string(), place: "Lake Charles".to_string() }), + ("KSHV".to_string(), Site { lat: 32.450813, long: -93.8412774, country: "USA".to_string(), state: "LA".to_string(), place: "Shreveport".to_string() }), + ("KLIX".to_string(), Site { lat: 30.3367133, long: -89.8256618, country: "USA".to_string(), state: "LA".to_string(), place: "New Orleans (Slidell)".to_string() }), + ("KBOX".to_string(), Site { lat: 41.9558919, long: -71.1369681, country: "USA".to_string(), state: "MA".to_string(), place: "Boston".to_string() }), + ("KGYX".to_string(), Site { lat: 43.8913555, long: -70.2565545, country: "USA".to_string(), state: "ME".to_string(), place: "Gray/Portland".to_string() }), + ("KCBW".to_string(), Site { lat: 46.0391944, long: -67.8066033, country: "USA".to_string(), state: "ME".to_string(), place: "Houlton".to_string() }), + ("KDTX".to_string(), Site { lat: 42.6999677, long: -83.471809, country: "USA".to_string(), state: "MI".to_string(), place: "Detroit/Pontiac".to_string() }), + ("KAPX".to_string(), Site { lat: 44.907106, long: -84.719817, country: "USA".to_string(), state: "MI".to_string(), place: "Gaylord".to_string() }), + ("KGRR".to_string(), Site { lat: 42.893872, long: -85.5449206, country: "USA".to_string(), state: "MI".to_string(), place: "Grand Rapids".to_string() }), + ("KMQT".to_string(), Site { lat: 46.5311443, long: -87.5487131, country: "USA".to_string(), state: "MI".to_string(), place: "Marquette".to_string() }), + ("KDLH".to_string(), Site { lat: 46.8368569, long: -92.2097433, country: "USA".to_string(), state: "MN".to_string(), place: "Duluth".to_string() }), + ("KMPX".to_string(), Site { lat: 44.8488029, long: -93.5654873, country: "USA".to_string(), state: "MN".to_string(), place: "Minneapolis/St. Paul".to_string() }), + ("KEAX".to_string(), Site { lat: 38.8102231, long: -94.2644924, country: "USA".to_string(), state: "MO".to_string(), place: "Kansas City".to_string() }), + ("KSGF".to_string(), Site { lat: 37.235223, long: -93.4006011, country: "USA".to_string(), state: "MO".to_string(), place: "Springfield".to_string() }), + ("KLSX".to_string(), Site { lat: 38.6986863, long: -90.682877, country: "USA".to_string(), state: "MO".to_string(), place: "St. Louis".to_string() }), + ("KDGX".to_string(), Site { lat: 32.2797358, long: -89.9846309, country: "USA".to_string(), state: "MS".to_string(), place: "Brandon/Jackson".to_string() }), + ("KGWX".to_string(), Site { lat: 33.8967796, long: -88.3293915, country: "USA".to_string(), state: "MS".to_string(), place: "Columbus AFB".to_string() }), + ("KBLX".to_string(), Site { lat: 45.8537632, long: -108.6068165, country: "USA".to_string(), state: "MT".to_string(), place: "Billings".to_string() }), + ("KGGW".to_string(), Site { lat: 48.2064536, long: -106.6252971, country: "USA".to_string(), state: "MT".to_string(), place: "Glasgow".to_string() }), + ("KTFX".to_string(), Site { lat: 47.4595023, long: -111.3855368, country: "USA".to_string(), state: "MT".to_string(), place: "Great Falls".to_string() }), + ("KMSX".to_string(), Site { lat: 47.0412971, long: -113.9864373, country: "USA".to_string(), state: "MT".to_string(), place: "Missoula".to_string() }), + ("KMHX".to_string(), Site { lat: 34.7759313, long: -76.8762571, country: "USA".to_string(), state: "NC".to_string(), place: "Morehead City".to_string() }), + ("KRAX".to_string(), Site { lat: 35.6654967, long: -78.4897855, country: "USA".to_string(), state: "NC".to_string(), place: "Raleigh/Durham".to_string() }), + ("KLTX".to_string(), Site { lat: 33.9891631, long: -78.4291059, country: "USA".to_string(), state: "NC".to_string(), place: "Wilmington".to_string() }), + ("KBIS".to_string(), Site { lat: 46.7709329, long: -100.7605532, country: "USA".to_string(), state: "ND".to_string(), place: "Bismarck".to_string() }), + ("KMVX".to_string(), Site { lat: 47.5279417, long: -97.3256654, country: "USA".to_string(), state: "ND".to_string(), place: "Grand Forks (Mayville)".to_string() }), + ("KMBX".to_string(), Site { lat: 48.39303, long: -100.8644378, country: "USA".to_string(), state: "ND".to_string(), place: "Minot AFB".to_string() }), + ("KUEX".to_string(), Site { lat: 40.320966, long: -98.4418559, country: "USA".to_string(), state: "NE".to_string(), place: "Grand Island/Hastings".to_string() }), + ("KLNX".to_string(), Site { lat: 41.9579623, long: -100.5759609, country: "USA".to_string(), state: "NE".to_string(), place: "North Platte".to_string() }), + ("KOAX".to_string(), Site { lat: 41.3202803, long: -96.3667971, country: "USA".to_string(), state: "NE".to_string(), place: "Omaha".to_string() }), + ("KABX".to_string(), Site { lat: 35.1497579, long: -106.8239576, country: "USA".to_string(), state: "NM".to_string(), place: "Albuquerque".to_string() }), + ("KFDX".to_string(), Site { lat: 34.6341569, long: -103.6186427, country: "USA".to_string(), state: "NM".to_string(), place: "Cannon AFB".to_string() }), + ("KHDX".to_string(), Site { lat: 33.0768844, long: -106.1200923, country: "USA".to_string(), state: "NM".to_string(), place: "Holloman AFB".to_string() }), + ("KLRX".to_string(), Site { lat: 40.7396933, long: -116.8025529, country: "USA".to_string(), state: "NV".to_string(), place: "Elko".to_string() }), + ("KESX".to_string(), Site { lat: 35.7012894, long: -114.8918277, country: "USA".to_string(), state: "NV".to_string(), place: "Las Vegas".to_string() }), + ("KRGX".to_string(), Site { lat: 39.7541931, long: -119.4620597, country: "USA".to_string(), state: "NV".to_string(), place: "Reno".to_string() }), + ("KENX".to_string(), Site { lat: 42.5865699, long: -74.0639877, country: "USA".to_string(), state: "NY".to_string(), place: "Albany".to_string() }), + ("KBGM".to_string(), Site { lat: 42.1997045, long: -75.9847015, country: "USA".to_string(), state: "NY".to_string(), place: "Binghamton".to_string() }), + ("KBUF".to_string(), Site { lat: 42.9488055, long: -78.7369108, country: "USA".to_string(), state: "NY".to_string(), place: "Buffalo".to_string() }), + ("KTYX".to_string(), Site { lat: 43.7556319, long: -75.6799918, country: "USA".to_string(), state: "NY".to_string(), place: "Montague".to_string() }), + ("KOKX".to_string(), Site { lat: 40.8655093, long: -72.8638548, country: "USA".to_string(), state: "NY".to_string(), place: "New York City".to_string() }), + ("KCLE".to_string(), Site { lat: 41.4131875, long: -81.8597451, country: "USA".to_string(), state: "OH".to_string(), place: "Cleveland".to_string() }), + ("KILN".to_string(), Site { lat: 39.42028, long: -83.82167, country: "USA".to_string(), state: "OH".to_string(), place: "Wilmington".to_string() }), + ("KFDR".to_string(), Site { lat: 34.3620014, long: -98.9766884, country: "USA".to_string(), state: "OK".to_string(), place: "Frederick".to_string() }), + ("KTLX".to_string(), Site { lat: 35.3333873, long: -97.2778255, country: "USA".to_string(), state: "OK".to_string(), place: "Oklahoma City".to_string() }), + ("KINX".to_string(), Site { lat: 36.1750977, long: -95.5642802, country: "USA".to_string(), state: "OK".to_string(), place: "Tulsa".to_string() }), + ("KVNX".to_string(), Site { lat: 36.7406166, long: -98.1279409, country: "USA".to_string(), state: "OK".to_string(), place: "Vance AFB".to_string() }), + ("KMAX".to_string(), Site { lat: 42.0810766, long: -122.7173334, country: "USA".to_string(), state: "OR".to_string(), place: "Medford".to_string() }), + ("KPDT".to_string(), Site { lat: 45.6906118, long: -118.8529301, country: "USA".to_string(), state: "OR".to_string(), place: "Pendleton".to_string() }), + ("KRTX".to_string(), Site { lat: 45.7150308, long: -122.9650542, country: "USA".to_string(), state: "OR".to_string(), place: "Portland".to_string() }), + ("KDIX".to_string(), Site { lat: 39.9470885, long: -74.4108027, country: "USA".to_string(), state: "PA".to_string(), place: "Philadelphia".to_string() }), + ("KPBZ".to_string(), Site { lat: 40.5316842, long: -80.2179515, country: "USA".to_string(), state: "PA".to_string(), place: "Pittsburgh".to_string() }), + ("KCCX".to_string(), Site { lat: 40.9228521, long: -78.0038738, country: "USA".to_string(), state: "PA".to_string(), place: "State College".to_string() }), + ("TJUA".to_string(), Site { lat: 18.1155998, long: -66.0780644, country: "USA".to_string(), state: "PR".to_string(), place: "San Juan".to_string() }), + ("KCLX".to_string(), Site { lat: 32.6554866, long: -81.0423124, country: "USA".to_string(), state: "SC".to_string(), place: "Charleston".to_string() }), + ("KCAE".to_string(), Site { lat: 33.9487579, long: -81.1184281, country: "USA".to_string(), state: "SC".to_string(), place: "Columbia".to_string() }), + ("KGSP".to_string(), Site { lat: 34.8833435, long: -82.2200757, country: "USA".to_string(), state: "SC".to_string(), place: "Greer".to_string() }), + ("KABR".to_string(), Site { lat: 45.4558185, long: -98.4132046, country: "USA".to_string(), state: "SD".to_string(), place: "Aberdeen".to_string() }), + ("KUDX".to_string(), Site { lat: 44.1248485, long: -102.8298157, country: "USA".to_string(), state: "SD".to_string(), place: "Rapid City".to_string() }), + ("KFSD".to_string(), Site { lat: 43.5877467, long: -96.7293674, country: "USA".to_string(), state: "SD".to_string(), place: "Sioux Falls".to_string() }), + ("KMRX".to_string(), Site { lat: 36.168538, long: -83.401779, country: "USA".to_string(), state: "TN".to_string(), place: "Knoxville/Tri Cities".to_string() }), + ("KNQA".to_string(), Site { lat: 35.3447802, long: -89.8734534, country: "USA".to_string(), state: "TN".to_string(), place: "Memphis".to_string() }), + ("KOHX".to_string(), Site { lat: 36.2472389, long: -86.5625185, country: "USA".to_string(), state: "TN".to_string(), place: "Nashville".to_string() }), + ("KAMA".to_string(), Site { lat: 35.2334827, long: -101.7092478, country: "USA".to_string(), state: "TX".to_string(), place: "Amarillo".to_string() }), + ("KEWX".to_string(), Site { lat: 29.7039802, long: -98.028506, country: "USA".to_string(), state: "TX".to_string(), place: "Austin/San Antonio".to_string() }), + ("KBRO".to_string(), Site { lat: 25.9159979, long: -97.4189526, country: "USA".to_string(), state: "TX".to_string(), place: "Brownsville".to_string() }), + ("KCRP".to_string(), Site { lat: 27.7840203, long: -97.511234, country: "USA".to_string(), state: "TX".to_string(), place: "Corpus Christi".to_string() }), + ("KFWS".to_string(), Site { lat: 32.5730186, long: -97.3031911, country: "USA".to_string(), state: "TX".to_string(), place: "Dallas/Ft. Worth".to_string() }), + ("KDYX".to_string(), Site { lat: 32.5386009, long: -99.2542863, country: "USA".to_string(), state: "TX".to_string(), place: "Dyess AFB".to_string() }), + ("KEPZ".to_string(), Site { lat: 31.8731115, long: -106.697942, country: "USA".to_string(), state: "TX".to_string(), place: "El Paso".to_string() }), + ("KGRK".to_string(), Site { lat: 30.7217637, long: -97.3829627, country: "USA".to_string(), state: "TX".to_string(), place: "Fort Hood".to_string() }), + ("KHGX".to_string(), Site { lat: 29.4718835, long: -95.0788593, country: "USA".to_string(), state: "TX".to_string(), place: "Houston/Galveston".to_string() }), + ("KDFX".to_string(), Site { lat: 29.2730823, long: -100.2802312, country: "USA".to_string(), state: "TX".to_string(), place: "Laughlin AFB".to_string() }), + ("KLBB".to_string(), Site { lat: 33.6541242, long: -101.814149, country: "USA".to_string(), state: "TX".to_string(), place: "Lubbock".to_string() }), + ("KMAF".to_string(), Site { lat: 31.9433953, long: -102.1894383, country: "USA".to_string(), state: "TX".to_string(), place: "Midland/Odessa".to_string() }), + ("KSJT".to_string(), Site { lat: 31.3712815, long: -100.4925227, country: "USA".to_string(), state: "TX".to_string(), place: "San Angelo".to_string() }), + ("KICX".to_string(), Site { lat: 37.59083, long: -112.86222, country: "USA".to_string(), state: "UT".to_string(), place: "Cedar City".to_string() }), + ("KMTX".to_string(), Site { lat: 41.2627795, long: -112.4480081, country: "USA".to_string(), state: "UT".to_string(), place: "Salt Lake City".to_string() }), + ("KAKQ".to_string(), Site { lat: 36.9840475, long: -77.007342, country: "USA".to_string(), state: "VA".to_string(), place: "Norfolk/Richmond".to_string() }), + ("KFCX".to_string(), Site { lat: 37.0242098, long: -80.2736664, country: "USA".to_string(), state: "VA".to_string(), place: "Roanoke".to_string() }), + ("KLWX".to_string(), Site { lat: 38.9753957, long: -77.4778444, country: "USA".to_string(), state: "VA".to_string(), place: "Sterling".to_string() }), + ("KCXX".to_string(), Site { lat: 44.5109941, long: -73.166424, country: "USA".to_string(), state: "VT".to_string(), place: "Burlington".to_string() }), + ("KLGX".to_string(), Site { lat: 47.116806, long: -124.10625, country: "USA".to_string(), state: "WA".to_string(), place: "Langley Hill".to_string() }), + ("KATX".to_string(), Site { lat: 48.1945614, long: -122.4957508, country: "USA".to_string(), state: "WA".to_string(), place: "Seattle/Tacoma".to_string() }), + ("KOTX".to_string(), Site { lat: 47.6803744, long: -117.6267797, country: "USA".to_string(), state: "WA".to_string(), place: "Spokane".to_string() }), + ("KGRB".to_string(), Site { lat: 44.4984644, long: -88.111124, country: "USA".to_string(), state: "WI".to_string(), place: "Green Bay".to_string() }), + ("KARX".to_string(), Site { lat: 43.822766, long: -91.1915767, country: "USA".to_string(), state: "WI".to_string(), place: "La Crosse".to_string() }), + ("KMKX".to_string(), Site { lat: 42.9678286, long: -88.5506335, country: "USA".to_string(), state: "WI".to_string(), place: "Milwaukee".to_string() }), + ("KRLX".to_string(), Site { lat: 38.3110763, long: -81.7229015, country: "USA".to_string(), state: "WV".to_string(), place: "Charleston".to_string() }), + ("KCYS".to_string(), Site { lat: 41.1519308, long: -104.8060325, country: "USA".to_string(), state: "WY".to_string(), place: "Cheyenne".to_string() }), + ("KRIW".to_string(), Site { lat: 43.0660779, long: -108.4773731, country: "USA".to_string(), state: "WY".to_string(), place: "Riverton".to_string() }) + ]) +} \ No newline at end of file diff --git a/nexrad-browser/src/utils.rs b/nexrad-browser/src/utils.rs index b1d7929..f67c61b 100644 --- a/nexrad-browser/src/utils.rs +++ b/nexrad-browser/src/utils.rs @@ -1,3 +1,5 @@ +use chrono::{DateTime, Duration, Utc}; + pub fn set_panic_hook() { // When the `console_error_panic_hook` feature is enabled, we can call the // `set_panic_hook` function at least once during initialization, and then @@ -8,3 +10,10 @@ pub fn set_panic_hook() { #[cfg(feature = "console_error_panic_hook")] console_error_panic_hook::set_once(); } + +pub const MILLIS_PER_DAY: u128 = 86_400_000; + +pub fn parse_date(julian: u32, millis: u32) -> DateTime { + let unix_time_millis = (julian as u128) * MILLIS_PER_DAY + millis as u128; + DateTime::UNIX_EPOCH + Duration::milliseconds(unix_time_millis as i64) +} \ No newline at end of file diff --git a/nexrad-browser/src/vcp.rs b/nexrad-browser/src/vcp.rs new file mode 100644 index 0000000..b9f165c --- /dev/null +++ b/nexrad-browser/src/vcp.rs @@ -0,0 +1,17 @@ +pub const fn vcp_string(vcp: i16) -> &'static str { + if vcp == 35 { + "CLEAR AIR MODE" + } else if vcp == 31 { + "CLEAR AIR MODE LONG-PULSE" + } else if vcp == 12 { + "PRECIPITATION MODE" + } else if vcp == 112 { + "PRECIPITATION MODE SZ-2 PRF" + } else if vcp == 212 { + "PRECIPITATION MODE SZ-2" + } else if vcp == 215 { + "PRECIPITATION MODE OPTIMIZED VERTICAL RANGE" + } else { + "UNKNOWN" + } +} \ No newline at end of file diff --git a/nexrad-browser/www/index.js b/nexrad-browser/www/index.js index 17f468d..45df9d2 100644 --- a/nexrad-browser/www/index.js +++ b/nexrad-browser/www/index.js @@ -1,37 +1,6 @@ import * as wasm from "./wasm/nexrad_browser.js"; await wasm.default(); -wasm.__nxrd_browser_init(); - -console.log("[JS] setup event listeners"); - -console.log("[JS] initializing the renderer"); - -const DEFAULT_PREFERENCES = { - RR: 5, - RREN: true, - FCS: 20 -}; -let preferences = DEFAULT_PREFERENCES; - -function get_font_size() { return `${preferences.FCS}px monospace`; } - -const canvas = document.getElementById("canvas"); - -function setupCanvas(canvas) { - // Get the device pixel ratio, falling back to 1. - var dpr = window.devicePixelRatio || 1; - // Get the size of the canvas in CSS pixels. - var rect = canvas.getBoundingClientRect(); - // Give the canvas pixel dimensions of their CSS - // size * the device pixel ratio. - canvas.width = rect.width * dpr; - canvas.height = rect.height * dpr; - var ctx = canvas.getContext('2d'); - // Scale all drawing operations by the dpr, so you - // don't have to worry about the difference. - ctx.scale(dpr, dpr); - return ctx; -} +let global_context = wasm.__nxrd_browser_init(); function rescaleCanvas(canvas, ctx) { var dpr = window.devicePixelRatio || 1; @@ -45,360 +14,30 @@ function rescaleCanvas(canvas, ctx) { // don't have to worry about the difference. ctx.scale(dpr, dpr); } +window.rescaleCanvas = rescaleCanvas; -const ctx = setupCanvas(canvas); - -const FIFTY_MILES = 0.0916; -let current_lat = 38.8977; -let current_long = -77.036560; - -function calcRenderbox() { - return [current_long - FIFTY_MILES, current_lat - FIFTY_MILES, current_long + FIFTY_MILES, current_lat + FIFTY_MILES]; -} - -function latlongXY(lat, long) { - let bbox = calcRenderbox(); - let pixelWidth = canvas.width; - let pixelHeight = canvas.height; - let bboxWidth = bbox[2] - bbox[0]; - let bboxHeight = bbox[3] - bbox[1]; - let widthPct = ( long - bbox[0] ) / bboxWidth; - let heightPct = ( lat - bbox[1] ) / bboxHeight; - let x = Math.floor( pixelWidth * widthPct ); - let y = Math.floor( pixelHeight * ( 1 - heightPct ) ); - return { x, y }; -} - -let x0 = 0; -let y0 = 0; -let xfull = canvas.width; -let yfull = canvas.height; - -function recalcBorderCoordinates() { - let xy = latlongXY(current_lat, current_long); - - x0 = xy.x - canvas.width; - y0 = xy.y - canvas.height; - xfull = x0 + canvas.width; - yfull = y0 + canvas.width; -} - -const red = "#ef0000"; -const green = "#4af626"; -const white = "#dedede"; - -let blinkyColor = "#dedede"; - -setInterval(() => { - if (blinkyColor === red) { - blinkyColor = white; - } else { - blinkyColor = red; - } -}, 350); - -function zulu() { - let date = new Date(); - return `${date.getUTCHours().toString().padStart(2, '0')}:${date.getUTCMinutes().toString().padStart(2, '0')}:${date.getUTCSeconds().toString().padStart(2, '0')}Z`; -} - -function vcp(vc) { - if (vc === 31) { return "CLEAR AIR MODE LONG PULSE"; } - else if (vc === 35) { return "CLEAR AIR MODE"; } - else if (vc === 12) { return "PRECIP MODE"; } - else if (vc === 112) { return "PRECIP MODE SZ-2 PRF"; } - else if (vc === 212) { return "PRECIP MODE SZ-2"; } - else if (vc === 215) { return "PRECIP MODE VERT"; } -} - -let command_buf = ""; -let buf_response_mode = false; - -let radar_inoperative = true; -let site_string = "SITE INFORMATION UNAVAILABLE"; -let icao = "INOP"; - -let selected_mode = "INOP"; - -let delay_string = "DELAY UNAVAILABLE"; - -// dataserver status: -// 0. disconnected -// 1. disconnected, one-off -// 2. track initializing -// 3. tracked -// 4. track lost - -let data_status = 0; - -function statusString() { - if (data_status === 0) { return "DISCONNECTED"; } - else if (data_status === 1) { return "ONEOFF"; } - else if (data_status === 2) { return "TRACK INITIALIZING"; } - else if (data_status === 3) { return "TRACKING"; } - else if (data_status === 4) { return "TRACK LOST"; } -} - -let display_buf = []; - -function recalcDisplayBuf() { - display_buf = []; - let line = 0; - for (let i = 0; i < command_buf.length; i++) { - let char = command_buf[i]; - if (char === "\n") { line += 1; continue; } - if (display_buf.length < line+1) { display_buf[line] = ""; } - display_buf[line] += char; - } -} - -function cmd_err(err) { - buf_response_mode = true; - command_buf = err; - recalcDisplayBuf(); -} - -let ar2 = undefined; -let new_file_available = false; - -document.getElementById("file").onchange = () => { - new_file_available = true; -} - -async function load() { - new_file_available = false; +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; - let loaded = wasm.load_ar2(data); - console.log(loaded); - cmd_err(""); - - ar2 = loaded; - data_status = 1; + wasm.load_ar2(data, global_context); }); reader.readAsArrayBuffer(file); + console.log(s); } -function exec(command) { - console.log("exec1!"); +// END GLOBAL FUNCTION DEFS - let tokens = command.split(" "); - - if (tokens[0] === "MODE" && tokens[1] === "SET") { - let mode = tokens[2]; - let valid_modes = ["REF", "VEL", "SW", "ZDR", "PHI", "RHO", "CFP"]; - if (!valid_modes.includes(mode)) { - cmd_err("INVALID MODE"); - return; - } else if (radar_inoperative) { - cmd_err("RADAR INOPERATIVE\nCANNOT SET MODE"); - return; - } else { - selected_mode = mode; - return; - } - } - - if (tokens[0] === "CLF" && tokens[1] === "OV") { - document.getElementById("file").click(); - return; - } - - if (tokens[0] === "CLF" && tokens[1] === "RELOAD") { - // TRIGGER RELOAD - cmd_err("SYSTEM PROCESSING"); - load(); - return; - } - - if (command === "TEST UNSET INOP") { - radar_inoperative = false; - icao = "TEST"; - site_string = "TEST VCP 000 TEST AIR MODE"; - selected_mode = "REF"; - return; - } - - if (command === "TEST INVALID COMMAND") { - cmd_err("TEST SUCCESSFUL"); - return; - } - - cmd_err("UNRECOGNIZED COMMAND"); +window.onkeydown = (e) => { + wasm.keydown(global_context, e.key); } -function shouldNewline() { - if (command_buf.startsWith("MODE SET")) { return true; } - - return false; +document.getElementById("file").onchange = () => { + wasm.new_file_available(global_context); } -let ar2_date = undefined; +let render = () => { wasm.render_abi(global_context); requestAnimationFrame(render); } -function convertDate() { - if (ar2 !== undefined) { - let unix_timestamp_millis = 86400000 * ar2.volume_header_record.date + ar2.volume_header_record.time; - ar2_date = new Date(unix_timestamp_millis); - } else { - return undefined; - } -} - -function reRender() { - if (ar2 !== undefined) { - radar_inoperative = false; - icao = ar2.volume_header_record.icao; - site_string = `${icao} VCP ${ar2.meta_rda_status_data.vcp} ${vcp(ar2.meta_rda_status_data.vcp)}`; - selected_mode = "REF"; - } else { - radar_inoperative = true; - icao = "INOP"; - site_string = "SITE INFORMATION UNAVAILABLE"; - selected_mode = "INOP"; - } - - convertDate(); - if (ar2_date !== undefined) { - const delay = Date.now() - ar2_date; - const SEC = 1000, MIN = 60 * SEC, HRS = 60 * MIN; - const humanDiff = `${Math.floor(delay/HRS)}:${Math.floor((delay%HRS)/MIN).toLocaleString('en-US', {minimumIntegerDigits: 2})}:${Math.floor((delay%MIN)/SEC).toLocaleString('en-US', {minimumIntegerDigits: 2})}`; - delay_string = "SITE DELAY " + humanDiff; - } - - //document.getElementById("input-detection").focus(); - rescaleCanvas(canvas, ctx); - ctx.clearRect(0, 0, canvas.width, canvas.height); - ctx.resetTransform(); - - recalcBorderCoordinates(); - - let xy = latlongXY(current_lat, current_long); - - ctx.translate(xy.x, xy.y); - - // background (black, always) - ctx.fillStyle = "black"; - ctx.fillRect(x0, y0, xfull * 2, yfull * 2); - - ctx.font = get_font_size(); - ctx.fillStyle = green; - ctx.fillText(`NEXRAD ${icao} ${zulu()}`, x0 + 50, y0 + 50); - - if (radar_inoperative) { - ctx.fillStyle = blinkyColor; - ctx.fillText("RADR INOP NO DATA LOADED", x0 + 50, y0 + 50 + preferences.FCS); - ctx.fillStyle = green; - } - if (new_file_available) { - ctx.fillStyle = blinkyColor; - ctx.fillText("NEW DATA AVAIL RLD RQD", x0 + 50, y0 + 50 + preferences.FCS * 2); - ctx.fillStyle = green; - } - - ctx.textAlign = "right"; - ctx.fillText("RADAR SITE", xfull - 75, y0 + 50); - ctx.fillText(`${site_string}`, xfull - 75, y0 + 50 + preferences.FCS); - ctx.fillText(`${delay_string}`, xfull - 75, y0 + 50 + preferences.FCS * 2); - ctx.fillText(`DATA SERVER ${statusString()}`, xfull - 75, y0 + 50 + preferences.FCS * 3); - - ctx.fillText("MODE", xfull - 75, y0 + canvas.height / 3); - - // this hurts me physically - - if (selected_mode === "REF") { - ctx.fillStyle = white; - ctx.fillText(">REF< ", xfull - 75, y0 + canvas.height / 3 + preferences.FCS); - ctx.fillStyle = green; - } else { - ctx.fillText(" REF ", xfull - 75, y0 + canvas.height / 3 + preferences.FCS); - } - - if (selected_mode === "VEL") { - ctx.fillStyle = white; - ctx.fillText(" >VEL< ", xfull - 75, y0 + canvas.height / 3 + preferences.FCS); - ctx.fillStyle = green; - } else { - ctx.fillText(" VEL ", xfull - 75, y0 + canvas.height / 3 + preferences.FCS); - } - - if (selected_mode === "SW") { - ctx.fillStyle = white; - ctx.fillText(" >SW <", xfull - 75, y0 + canvas.height / 3 + preferences.FCS); - ctx.fillStyle = green; - } else { - ctx.fillText(" SW ", xfull - 75, y0 + canvas.height / 3 + preferences.FCS); - } - - if (selected_mode === "ZDR") { - ctx.fillStyle = white; - ctx.fillText(">ZDR< ", xfull - 75, y0 + canvas.height / 3 + preferences.FCS*2); - ctx.fillStyle = green; - } else { - ctx.fillText(" ZDR ", xfull - 75, y0 + canvas.height / 3 + preferences.FCS*2); - } - - if (selected_mode === "PHI") { - ctx.fillStyle = white; - ctx.fillText(" >PHI< ", xfull - 75, y0 + canvas.height / 3 + preferences.FCS*2); - ctx.fillStyle = green; - } else { - ctx.fillText(" PHI ", xfull - 75, y0 + canvas.height / 3 + preferences.FCS*2); - } - - if (selected_mode === "RHO") { - ctx.fillStyle = white; - ctx.fillText(" >RHO<", xfull - 75, y0 + canvas.height / 3 + preferences.FCS*2); - ctx.fillStyle = green; - } else { - ctx.fillText(" RHO ", xfull - 75, y0 + canvas.height / 3 + preferences.FCS*2); - } - - if (selected_mode === "CFP") { - ctx.fillStyle = white; - ctx.fillText(" >CFP< ", xfull - 75, y0 + canvas.height / 3 + preferences.FCS*3); - ctx.fillStyle = green; - } else { - ctx.fillText(" CFP ", xfull - 75, y0 + canvas.height / 3 + preferences.FCS*3); - } - - if (radar_inoperative) { - ctx.fillStyle = red; - ctx.fillText(" >RADR INOP<", xfull - 75, y0 + canvas.height / 3 + preferences.FCS * 3); - ctx.fillStyle = green; - } - - - ctx.textAlign = "left"; - - for (let line = 0; line < display_buf.length; line++) { - ctx.fillText(display_buf[line], x0 + 50, y0 + canvas.height / 2 + (preferences.FCS * line)); - } -} - -setInterval(reRender, 10); - -document.onkeyup = (e) => { - if (e.key.toUpperCase() === "ESCAPE") { - command_buf = ""; - } else if (e.key.toUpperCase() === "ENTER") { - let command = command_buf.replace("\n", " "); - command_buf = ""; - exec(command); - } else if (e.key.toUpperCase() === "BACKSPACE") { - command_buf = command_buf.slice(0, command_buf.length - 1); - } else if (e.key.toUpperCase() === " " && shouldNewline()) { - command_buf += "\n"; - } else { - if (e.key.length !== 1) { return; } - if (buf_response_mode) { - buf_response_mode = false; - command_buf = ""; - } - command_buf += e.key.toUpperCase(); - } - recalcDisplayBuf(); - reRender(); -} \ No newline at end of file +requestAnimationFrame(render); \ No newline at end of file diff --git a/nexrad2/src/message2.rs b/nexrad2/src/message2.rs index 1428577..91661ee 100644 --- a/nexrad2/src/message2.rs +++ b/nexrad2/src/message2.rs @@ -6,34 +6,34 @@ pub const MSG_RDA_STATUS_DATA: u8 = 2; #[derive(Debug, Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Msg02RDAStatusData { - rda_status: u16, - operability_status: u16, - control_status: u16, - aux_power_state: u16, - average_transmitter_power_watts: u16, - horiz_ref_calib_corr: u16, - data_tx_enabled: u16, - vcp: i16, - rdaca: u16, - rda_build_no: u16, - opmode: u16, - super_resolution_status: u16, - clutter_mitigation_status: u16, - rda_scan_data_flags: u16, - rda_alarm_summary: u16, - command_ack: u16, - channel_con_stat: u16, - spot_blank_stat: u16, - bypass_map_gen_date: u16, - bypass_map_gen_time: u16, - clutter_map_gen_date: u16, - clutter_map_gen_time: u16, - vert_ref_calib_corr: u16, - tps_status: u16, - rms_control_status: u16, - perf_check_status: u16, - alarm: u16, - extra: [u8; 8], + pub rda_status: u16, + pub operability_status: u16, + pub control_status: u16, + pub aux_power_state: u16, + pub average_transmitter_power_watts: u16, + pub horiz_ref_calib_corr: u16, + pub data_tx_enabled: u16, + pub vcp: i16, + pub rdaca: u16, + pub rda_build_no: u16, + pub opmode: u16, + pub super_resolution_status: u16, + pub clutter_mitigation_status: u16, + pub rda_scan_data_flags: u16, + pub rda_alarm_summary: u16, + pub command_ack: u16, + pub channel_con_stat: u16, + pub spot_blank_stat: u16, + pub bypass_map_gen_date: u16, + pub bypass_map_gen_time: u16, + pub clutter_map_gen_date: u16, + pub clutter_map_gen_time: u16, + pub vert_ref_calib_corr: u16, + pub tps_status: u16, + pub rms_control_status: u16, + pub perf_check_status: u16, + pub alarm: u16, + pub extra: [u8; 8], } impl FromBody<68> for Msg02RDAStatusData { diff --git a/nexrad2/src/message31.rs b/nexrad2/src/message31.rs index e7e6f5a..e6118f9 100644 --- a/nexrad2/src/message31.rs +++ b/nexrad2/src/message31.rs @@ -4,6 +4,7 @@ use std::fmt::{Debug, Formatter}; pub const MSG31_HEADER_LENGTH: usize = 4 + 4 + 2 + 2 + 4 + 1 + 1 + 2 + 1 + 1 + 1 + 1 + 4 + 1 + 1 + 2; #[derive(Debug)] +#[cfg_attr(feature = "serde_derive", derive(serde::Serialize, serde::Deserialize))] pub struct Message31Header { pub radar_identifier: String, pub collection_time: u32, @@ -26,6 +27,7 @@ pub struct Message31Header { pub const VOLUME_DATA_LENGTH: usize = 1 + 3 + 2 + 1 + 1 + 4 + 4 + 2 + 2 + 4 + 4 + 4 + 4 + 4 + 2 + 2; #[derive(Debug)] +#[cfg_attr(feature = "serde_derive", derive(serde::Serialize, serde::Deserialize))] pub struct VolumeData { pub datablock_type: String, pub data_name: String, @@ -48,6 +50,7 @@ pub struct VolumeData { pub const ELEVATION_DATA_LENGTH: usize = 1 + 3 + 2 + 2 + 4; #[derive(Debug)] +#[cfg_attr(feature = "serde_derive", derive(serde::Serialize, serde::Deserialize))] pub struct ElevationData { pub datablock_type: String, pub data_name: String, @@ -59,6 +62,7 @@ pub struct ElevationData { pub const RADIAL_DATA_LENGTH: usize = 1 + 3 + 2 + 2 + 4 + 4 + 2 + 2 + 4 + 4; #[derive(Debug)] +#[cfg_attr(feature = "serde_derive", derive(serde::Serialize, serde::Deserialize))] pub struct RadialData { pub datablock_type: String, pub data_name: String, @@ -75,6 +79,7 @@ pub struct RadialData { pub const GENERIC_DATA_MOMENT_LEN: usize = 1 + 3 + 4 + 2 + 2 + 2 + 2 + 2 + 1 + 1 + 4 + 4; #[derive(Debug)] +#[cfg_attr(feature = "serde_derive", derive(serde::Serialize, serde::Deserialize))] pub struct GenericDataMoment { pub datablock_type: String, pub data_name: String, @@ -90,6 +95,7 @@ pub struct GenericDataMoment { pub offset: f32 } +#[cfg_attr(feature = "serde_derive", derive(serde::Serialize, serde::Deserialize))] pub struct DataMoment { pub gdm: GenericDataMoment, pub data: Vec @@ -102,6 +108,7 @@ impl Debug for DataMoment { } #[derive(Debug)] +#[cfg_attr(feature = "serde_derive", derive(serde::Serialize, serde::Deserialize))] pub struct Message31 { pub header: Message31Header, pub volume_info: Option,