diff --git a/nexrad-browser/Cargo.toml b/nexrad-browser/Cargo.toml index a4972ce..b01883d 100644 --- a/nexrad-browser/Cargo.toml +++ b/nexrad-browser/Cargo.toml @@ -26,6 +26,7 @@ winit = { version = "0.29", features = ["rwh_05"], default-features = false } wgpu = { version = "0.18", features = ["webgl"] } raw-window-handle = "0.5" wasm-bindgen-futures = "0.4" +glyphon = { git = "https://github.com/grovesNL/glyphon" } [dependencies.js-sys] version = "0.3" diff --git a/nexrad-browser/src/NotoSansMono-Regular.ttf b/nexrad-browser/src/NotoSansMono-Regular.ttf new file mode 100644 index 0000000..43fec16 Binary files /dev/null and b/nexrad-browser/src/NotoSansMono-Regular.ttf differ diff --git a/nexrad-browser/src/VT323-Regular.ttf b/nexrad-browser/src/VT323-Regular.ttf new file mode 100644 index 0000000..6aec599 Binary files /dev/null and b/nexrad-browser/src/VT323-Regular.ttf differ diff --git a/nexrad-browser/src/colors.rs b/nexrad-browser/src/colors.rs index eb971a8..0f573bd 100644 --- a/nexrad-browser/src/colors.rs +++ b/nexrad-browser/src/colors.rs @@ -1,5 +1,5 @@ -use nexrad2::message31::MOMENT_DATA_FOLDED; use crate::mode::Mode; +use nexrad2::message31::MOMENT_DATA_FOLDED; pub fn correlation_coefficient(val: f32) -> &'static str { let gradient = [ @@ -24,7 +24,7 @@ pub fn correlation_coefficient(val: f32) -> &'static str { (1.02, "purple"), (1.03, "mediumvioletred"), (1.045, "pink"), - (1.05, "lavenderblush") + (1.05, "lavenderblush"), ]; for (threshold, color) in gradient { @@ -61,7 +61,7 @@ pub fn spectrum_width(val: f32) -> &'static str { (32.0, "white"), (35.0, "yellow"), (60.0, "lime"), - (1000.0, "purple") + (1000.0, "purple"), ]; for (threshold, color) in gradient { @@ -96,7 +96,7 @@ pub fn differential_reflectivity(val: f32) -> &'static str { (6.0, "maroon"), (7.0, "hotpink"), (10.0, "pink"), - (999.0, "white") + (999.0, "white"), ]; for (threshold, color) in gradient { @@ -183,7 +183,13 @@ pub fn velocity(vel: f32) -> &'static str { "#520106", // 140 ]; - let i = scale_int((vel.floor()) as i32, 140, -140, (colors.len() - 1) as i32, 0); + let i = scale_int( + (vel.floor()) as i32, + 140, + -140, + (colors.len() - 1) as i32, + 0, + ); colors[i as usize] } @@ -201,6 +207,6 @@ pub fn color_scheme(product: Mode, value: f32) -> &'static str { Mode::DifferentialPhase => correlation_coefficient(value), Mode::CorrelationCoefficient => correlation_coefficient(value), Mode::ClutterFilterPowerRemoved => dbz_noaa(value), - Mode::RadarInoperative => "#ff0000" + Mode::RadarInoperative => "#ff0000", } -} \ No newline at end of file +} diff --git a/nexrad-browser/src/command.rs b/nexrad-browser/src/command.rs index 141d5a8..ce1302c 100644 --- a/nexrad-browser/src/command.rs +++ b/nexrad-browser/src/command.rs @@ -1,11 +1,10 @@ -use web_sys::FileReader; use crate::loadar2; use crate::mode::Mode; use crate::scope::ScopeState; +use web_sys::FileReader; pub fn should_newline(state: &mut ScopeState) -> bool { - state.command_buf.starts_with("MODE SET") - || state.command_buf.starts_with("ELEVATION SET") + state.command_buf.starts_with("MODE SET") || state.command_buf.starts_with("ELEVATION SET") } pub fn exec(state: &mut ScopeState, command: String) { @@ -20,7 +19,7 @@ pub fn exec(state: &mut ScopeState, command: String) { if tokens.len() < 3 { state.command_buf = "ARGUMENT INVALID".to_string(); state.command_buf_response_mode = true; - return + return; } else { let mode = tokens[2]; if let Some(ar2) = &state.ar2 { @@ -30,7 +29,7 @@ pub fn exec(state: &mut ScopeState, command: String) { if !valid_modes.contains(&&mode.to_string()) { state.command_buf = "ARGUMENT INVALID".to_string(); state.command_buf_response_mode = true; - return + return; } state.scope_mode = match mode { @@ -41,19 +40,19 @@ pub fn exec(state: &mut ScopeState, command: String) { "PHI" => Mode::DifferentialPhase, "RHO" => Mode::CorrelationCoefficient, "CFP" => Mode::ClutterFilterPowerRemoved, - _ => unreachable!() + _ => unreachable!(), }; } else { state.command_buf = "CANNOT SET MODE\nRADAR INOPERATIVE".to_string(); state.command_buf_response_mode = true; - return + return; } } } else if command.starts_with("ELEVATION SET") { if tokens.len() < 3 { state.command_buf = "ARGUMENT INVALID".to_string(); state.command_buf_response_mode = true; - return + return; } else { let elev = tokens[2].to_string(); if let Some(ar2) = &state.ar2 { @@ -64,7 +63,7 @@ pub fn exec(state: &mut ScopeState, command: String) { if !valid_elevs.contains(&elev) { state.command_buf = "ARGUMENT INVALID".to_string(); state.command_buf_response_mode = true; - return + return; } let number: usize = elev.parse().unwrap(); @@ -74,7 +73,7 @@ pub fn exec(state: &mut ScopeState, command: String) { } else { state.command_buf = "CANNOT SET MODE\nRADAR INOPERATIVE".to_string(); state.command_buf_response_mode = true; - return + return; } } } else if command == "`T" { @@ -83,4 +82,4 @@ pub fn exec(state: &mut ScopeState, command: String) { 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 index d3da646..f385bb3 100644 --- a/nexrad-browser/src/equirectangular.rs +++ b/nexrad-browser/src/equirectangular.rs @@ -3,5 +3,8 @@ 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 + ( + EARTH_RADIUS_MILES * long * lat.cos(), + EARTH_RADIUS_MILES * lat, + ) +} diff --git a/nexrad-browser/src/lib.rs b/nexrad-browser/src/lib.rs index 66c3877..dea03d1 100644 --- a/nexrad-browser/src/lib.rs +++ b/nexrad-browser/src/lib.rs @@ -1,31 +1,32 @@ -pub mod utils; +pub mod colors; +pub mod command; +pub mod equirectangular; pub mod mode; pub mod scope; -pub mod equirectangular; pub mod sites; -pub mod command; +pub mod text; +pub mod utils; pub mod vcp; -pub mod colors; -use std::io::Cursor; -use std::ops::{Deref, DerefMut}; -use js_sys::Uint8Array; -use log::{debug, error, info}; -use wasm_bindgen::prelude::*; -use web_sys::{CanvasRenderingContext2d, HtmlCanvasElement}; -use wgpu::SurfaceError; -use winit::dpi::PhysicalSize; -use winit::event::{ElementState, Event, WindowEvent}; -use winit::event::WindowEvent::KeyboardInput; -use winit::event_loop::{EventLoop, EventLoopWindowTarget}; -use winit::keyboard::KeyCode; -use winit::window::WindowBuilder; -use winit::platform::web::{WindowExtWebSys, WindowBuilderExtWebSys, EventLoopExtWebSys}; use crate::command::{exec, should_newline}; use crate::mode::Mode; use crate::mode::Mode::Reflectivity; use crate::scope::{Preferences, ScopeState, WgpuState}; - +use glyphon::{Metrics, TextBounds}; +use js_sys::Uint8Array; +use log::{debug, error, info}; +use std::io::Cursor; +use std::ops::{Deref, DerefMut}; +use wasm_bindgen::prelude::*; +use web_sys::{CanvasRenderingContext2d, HtmlCanvasElement}; +use wgpu::SurfaceError; +use winit::dpi::PhysicalSize; +use winit::event::WindowEvent::KeyboardInput; +use winit::event::{ElementState, Event, WindowEvent}; +use winit::event_loop::{EventLoop, EventLoopBuilder, EventLoopProxy, EventLoopWindowTarget}; +use winit::keyboard::KeyCode; +use winit::platform::web::{EventLoopExtWebSys, WindowBuilderExtWebSys, WindowExtWebSys}; +use winit::window::WindowBuilder; #[wasm_bindgen] extern "C" { @@ -33,22 +34,31 @@ extern "C" { fn loadar2(c: &str); } -#[wasm_bindgen] -pub struct AbiScopeState(ScopeState); +#[derive(Debug, Clone, PartialEq)] +pub enum RenderMessage { + Resize { new_w: u32, new_h: u32 }, +} #[wasm_bindgen] -pub async fn __nxrd_browser_init(w: u32, h: u32) -> AbiScopeState { +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)); utils::set_panic_hook(); info!("wgpu setup"); - let event_loop = EventLoop::new().expect("event loop creation failed"); - let window = WindowBuilder::new().with_prevent_default(false).build(&event_loop).unwrap(); + let event_loop = EventLoopBuilder::::with_user_event() + .build() + .expect("event loop creation failed"); + let window = WindowBuilder::new() + .with_prevent_default(false) + .build(&event_loop) + .unwrap(); window.set_min_inner_size(Some(PhysicalSize::new(w, h))); - web_sys::window() .and_then(|win| win.document()) .and_then(|doc| { @@ -62,70 +72,115 @@ pub async fn __nxrd_browser_init(w: u32, h: u32) -> AbiScopeState { let mut render_state = WgpuState::new(window, PhysicalSize::new(w, h)).await; - - // If you see an error here, your IDE is not compiling for webassembly - event_loop.spawn(move |event: Event<_>, window: &EventLoopWindowTarget<_>| { - match event { - Event::WindowEvent { ref event, window_id} => { - match event { - KeyboardInput { event, .. } => { - debug!("{:?}", event.physical_key); - }, - WindowEvent::CloseRequested => { window.exit(); }, - WindowEvent::Resized(physical_size) => { - render_state.reconfigure(*physical_size); - }, - WindowEvent::RedrawRequested => { - render_state.update(); - match render_state.render() { - Ok(_) => {}, - Err(SurfaceError::Lost) => render_state.reconfigure(render_state.size), - Err(SurfaceError::OutOfMemory) => { - error!("out of memory!"); - window.exit(); - }, - Err(e) => error!("transient rendering error: {}", e), - } - }, - _ => {} - } - }, - Event::AboutToWait => { - render_state.window().request_redraw(); - }, - _ => {} - } - }); - info!("initializing the scope"); - let document = web_sys::window().expect("window should exist").document().expect("document should exist"); + let document = web_sys::window() + .expect("window should exist") + .document() + .expect("document should exist"); 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 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 { + let mut scope_state = ScopeState { ar2: None, scope_mode: Mode::RadarInoperative, file_input: file, - lat: 30.48500, // jacksonville + lat: 30.48500, // jacksonville long: -81.70200, // jacksonville - prefs: Preferences { - fcs: 20 - }, + prefs: Preferences { fcs: 20 }, command_buf: String::new(), command_buf_response_mode: false, new_data_available: false, selected_elevation: 1, - show_ui: false + show_ui: false, + wgpu: render_state, }; + scope_state.wgpu.add_textarea( + "status_bar".to_string(), + Metrics::new(30.0, 42.0), + 10.0, + 10.0, + 1.0, + TextBounds { + left: 0, + top: 0, + right: 600, + bottom: 160, + }, + glyphon::Color::rgb(0x4a, 0xf6, 0x26), + "NEXRAD INOP 00:00:00Z", + ); + + let event_proxy: EventLoopProxy = event_loop.create_proxy(); + + // If you see an error here, your IDE is not compiling for webassembly + event_loop.spawn( + move |event: Event<_>, window: &EventLoopWindowTarget<_>| match event { + Event::UserEvent(e) => match e { + RenderMessage::Resize { new_w, new_h } => { + scope_state + .wgpu + .reconfigure(PhysicalSize::new(new_w, new_h)); + } + }, + Event::WindowEvent { + ref event, + window_id, + } => match event { + KeyboardInput { event, .. } => { + debug!("{:?}", event.physical_key); + } + WindowEvent::CloseRequested => { + window.exit(); + } + WindowEvent::Resized(physical_size) => { + scope_state.wgpu.reconfigure(*physical_size); + } + WindowEvent::RedrawRequested => { + scope_state.wgpu.update(); + match scope_state.wgpu.render() { + Ok(_) => {} + Err(SurfaceError::Lost) => { + scope_state.wgpu.reconfigure(scope_state.wgpu.size) + } + Err(SurfaceError::OutOfMemory) => { + error!("out of memory!"); + window.exit(); + } + Err(e) => error!("transient rendering error: {}", e), + } + } + _ => {} + }, + Event::AboutToWait => { + scope_state.wgpu.window().request_redraw(); + } + _ => {} + }, + ); + info!("nexrad-browser initialized successfully"); - AbiScopeState(scope_state) + AbiProxy(event_proxy) } + +#[wasm_bindgen] +pub fn resize_abi(w: u32, h: u32, proxy: &mut AbiProxy) { + proxy + .0 + .send_event(RenderMessage::Resize { new_w: w, new_h: h }) + .unwrap(); +} + /* #[wasm_bindgen] pub fn render_abi(state: &mut AbiScopeState) { @@ -133,7 +188,7 @@ pub fn render_abi(state: &mut AbiScopeState) { } */ - +/* #[wasm_bindgen] pub fn load_ar2(buf: &JsValue, scope: &mut AbiScopeState) { let array = Uint8Array::new(buf); @@ -174,4 +229,6 @@ pub fn keydown(state: &mut AbiScopeState, key: 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 index a6f245e..acd8629 100644 --- a/nexrad-browser/src/mode.rs +++ b/nexrad-browser/src/mode.rs @@ -15,7 +15,7 @@ pub enum Mode { /// CFP ClutterFilterPowerRemoved, /// RADR INOP - RadarInoperative + RadarInoperative, } impl Mode { pub fn unselected(&self) -> &str { @@ -27,7 +27,7 @@ impl Mode { Self::DifferentialPhase => " PHI ", Self::CorrelationCoefficient => " RHO ", Self::ClutterFilterPowerRemoved => " CFP ", - Self::RadarInoperative => " RADR INOP" + Self::RadarInoperative => " RADR INOP", } } pub fn selected(&self) -> &str { @@ -39,10 +39,10 @@ impl Mode { Self::DifferentialPhase => ">PHI<", Self::CorrelationCoefficient => ">RHO<", Self::ClutterFilterPowerRemoved => ">CFP<", - Self::RadarInoperative => ">RADR INOP<" + Self::RadarInoperative => ">RADR INOP<", } } - + pub fn rname(&self) -> &str { match self { Self::Reflectivity => "REF", @@ -52,7 +52,7 @@ impl Mode { Self::DifferentialPhase => "PHI", Self::CorrelationCoefficient => "RHO", Self::ClutterFilterPowerRemoved => "CFP", - Self::RadarInoperative => "NOP" + Self::RadarInoperative => "NOP", } } -} \ No newline at end of file +} diff --git a/nexrad-browser/src/old_rendering.rs b/nexrad-browser/src/old_rendering.rs index 08c6147..0757781 100644 --- a/nexrad-browser/src/old_rendering.rs +++ b/nexrad-browser/src/old_rendering.rs @@ -1,16 +1,19 @@ -use std::f64::consts::PI; -use chrono::{DateTime, Timelike, Utc}; -use itertools::Itertools; -use log::{debug, info}; -use wasm_bindgen::JsValue; -use nexrad2::message31::MOMENT_DATA_BELOW_THRESHOLD; use crate::colors::color_scheme; use crate::mode::Mode; -use crate::mode::Mode::{ClutterFilterPowerRemoved, CorrelationCoefficient, DifferentialPhase, DifferentialReflectivity, RadarInoperative, Reflectivity, SpectrumWidth, Velocity}; +use crate::mode::Mode::{ + ClutterFilterPowerRemoved, CorrelationCoefficient, DifferentialPhase, DifferentialReflectivity, + RadarInoperative, Reflectivity, SpectrumWidth, Velocity, +}; use crate::rescaleCanvas; use crate::scope::ScopeState; use crate::utils::parse_date; use crate::vcp::vcp_string; +use chrono::{DateTime, Timelike, Utc}; +use itertools::Itertools; +use log::{debug, info}; +use nexrad2::message31::MOMENT_DATA_BELOW_THRESHOLD; +use std::f64::consts::PI; +use wasm_bindgen::JsValue; pub const TEXT_COLOR_RED: &str = "#ef0000"; pub const TEXT_COLOR_GREEN: &str = "#4af626"; @@ -52,24 +55,48 @@ pub fn render(state: &mut ScopeState) -> Result<(), JsValue> { // render out the scope id line if state.show_ui { - 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)?; + 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.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.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)?; + ctx.fill_text( + line, + 50.0, + (canvas.height() / 3) as f64 + (state.prefs.fcs * line_no) as f64, + )?; } // render the radar site info @@ -77,7 +104,16 @@ pub fn render(state: &mut ScopeState) -> Result<(), JsValue> { 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))?; + 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; @@ -85,18 +121,39 @@ pub fn render(state: &mut ScopeState) -> Result<(), JsValue> { let minutes = (delay.num_seconds() / 60) % 60; let hours = delay.num_seconds() / 3600; - ctx.fill_text(&format!("SITE DELAY {:0>2}:{:0>2}:{:0>2}", hours, minutes, seconds), (canvas.width() - 50) as f64, 50.0 + (state.prefs.fcs as f64 * 2.0))?; + ctx.fill_text( + &format!("SITE DELAY {:0>2}:{:0>2}:{:0>2}", hours, minutes, seconds), + (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)?; + 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, + )?; } // mode info - ctx.fill_text("MODE", (canvas.width() - 50) as f64, (canvas.height() / 3) as f64)?; + ctx.fill_text( + "MODE", + (canvas.width() - 50) as f64, + (canvas.height() / 3) as f64, + )?; let modelines = [ [Reflectivity, Velocity, SpectrumWidth].as_slice(), - [DifferentialReflectivity, DifferentialPhase, CorrelationCoefficient].as_slice(), - [ClutterFilterPowerRemoved, RadarInoperative].as_slice() + [ + DifferentialReflectivity, + DifferentialPhase, + CorrelationCoefficient, + ] + .as_slice(), + [ClutterFilterPowerRemoved, RadarInoperative].as_slice(), ]; let mut available_modes = vec![]; @@ -114,7 +171,11 @@ pub fn render(state: &mut ScopeState) -> Result<(), JsValue> { let pad_end = 2 - item_no; let pad_start = " ".repeat(pad_start * 6); let pad_end = " ".repeat(pad_end * 6); - let middle = if state.scope_mode == *item { item.selected() } else { item.unselected() }; + let middle = if state.scope_mode == *item { + item.selected() + } else { + item.unselected() + }; if state.scope_mode == *item { // display selected color @@ -130,7 +191,6 @@ pub fn render(state: &mut ScopeState) -> Result<(), JsValue> { continue; } - if available_modes.contains(&&item.rname().to_string()) { ctx.fill_text(&format!(" {}{}{}", pad_start, middle, pad_end), x, y)?; } @@ -142,26 +202,56 @@ pub fn render(state: &mut ScopeState) -> Result<(), JsValue> { } } - ctx.fill_text("ELEVATIONS", (canvas.width() - 50) as f64, ((canvas.height() / 7) * 4) as f64)?; + ctx.fill_text( + "ELEVATIONS", + (canvas.width() - 50) as f64, + ((canvas.height() / 7) * 4) as f64, + )?; if let Some(ar2) = &state.ar2 { let mut line_no = 0; - for chunk in &ar2.elevations.iter().sorted_by(|a, b| { - Ord::cmp(&a.0, &b.0) - }).enumerate().chunks(4) { + for chunk in &ar2 + .elevations + .iter() + .sorted_by(|a, b| Ord::cmp(&a.0, &b.0)) + .enumerate() + .chunks(4) + { for (no_in_line, (elevation_no, scans)) in chunk { if state.selected_elevation != *elevation_no { - ctx.fill_text(&format!(" {:0>3} {}", elevation_no, " ".repeat(5 * (3 - (no_in_line % 4)))), (canvas.width() - 50) as f64, ((canvas.height() / 7) * 4) as f64 + (state.prefs.fcs * (line_no + 1)) as f64)?; + ctx.fill_text( + &format!( + " {:0>3} {}", + elevation_no, + " ".repeat(5 * (3 - (no_in_line % 4))) + ), + (canvas.width() - 50) as f64, + ((canvas.height() / 7) * 4) as f64 + + (state.prefs.fcs * (line_no + 1)) as f64, + )?; } else { ctx.set_fill_style(&JsValue::from_str(TEXT_COLOR_WHITE)); - ctx.fill_text(&format!(">{:0>3}<{}", elevation_no, " ".repeat(5 * (3 - (no_in_line % 4)))), (canvas.width() - 50) as f64, ((canvas.height() / 7) * 4) as f64 + (state.prefs.fcs * (line_no + 1)) as f64)?; + ctx.fill_text( + &format!( + ">{:0>3}<{}", + elevation_no, + " ".repeat(5 * (3 - (no_in_line % 4))) + ), + (canvas.width() - 50) as f64, + ((canvas.height() / 7) * 4) as f64 + + (state.prefs.fcs * (line_no + 1)) as f64, + )?; ctx.set_fill_style(&JsValue::from_str(TEXT_COLOR_GREEN)); } } line_no += 1; } } else { - ctx.fill_text("ELEVATION INFORMATION UNAVAILABLE", (canvas.width() - 50) as f64, ((canvas.height() / 7) * 4) as f64 + state.prefs.fcs as f64)?; + ctx.fill_text( + "ELEVATION INFORMATION UNAVAILABLE", + (canvas.width() - 50) as f64, + ((canvas.height() / 7) * 4) as f64 + state.prefs.fcs as f64, + )?; } } @@ -173,8 +263,21 @@ pub fn render(state: &mut ScopeState) -> Result<(), JsValue> { let radials = ar2.elevations.get(&state.selected_elevation).unwrap(); - let first_gate_px = (radials[0].available_data.get("REF").expect("reflectivity is missing!").gdm.data_moment_range) as f32 / 1000.0 * px_per_km; - let gate_interval_km = (radials[0].available_data.get("REF").expect("reflectivity is missing!").gdm.data_moment_range_sample_interval) as f32 / 1000.0; + let first_gate_px = (radials[0] + .available_data + .get("REF") + .expect("reflectivity is missing!") + .gdm + .data_moment_range) as f32 + / 1000.0 + * px_per_km; + let gate_interval_km = (radials[0] + .available_data + .get("REF") + .expect("reflectivity is missing!") + .gdm + .data_moment_range_sample_interval) as f32 + / 1000.0; let gate_width_px = gate_interval_km * px_per_km; for radial in radials { @@ -188,7 +291,7 @@ pub fn render(state: &mut ScopeState) -> Result<(), JsValue> { let azimuth_spacing = match radial.header.azimuth_resolution_spacing { 1 => 0.5, - _ => 1.0 + _ => 1.0, }; if (azimuth_angle + azimuth_spacing).floor() > azimuth { azimuth += azimuth_spacing; @@ -205,26 +308,60 @@ pub fn render(state: &mut ScopeState) -> Result<(), JsValue> { // line width // line cap - let gates = radial.available_data.get(state.scope_mode.rname()).expect("selected unavailable product").scaled_data(); + let gates = radial + .available_data + .get(state.scope_mode.rname()) + .expect("selected unavailable product") + .scaled_data(); let num_gates = gates.len(); for (num, value) in gates.iter().enumerate() { if *value != MOMENT_DATA_BELOW_THRESHOLD { - - ctx.move_to(xc as f64 + start_angle.cos() * distance_x as f64, yc as f64 + start_angle.sin() * distance_y as f64); + ctx.move_to( + xc as f64 + start_angle.cos() * distance_x as f64, + yc as f64 + start_angle.sin() * distance_y as f64, + ); ctx.begin_path(); if num == 0 { - ctx.ellipse(xc as f64, yc as f64, distance_x as f64, distance_y as f64, 0.0, start_angle - 0.001, end_angle + 0.001)?; + ctx.ellipse( + xc as f64, + yc as f64, + distance_x as f64, + distance_y as f64, + 0.0, + start_angle - 0.001, + end_angle + 0.001, + )?; } else if num == num_gates - 1 { - ctx.ellipse(xc as f64, yc as f64, distance_x as f64, distance_y as f64, 0.0, start_angle, end_angle)?; + ctx.ellipse( + xc as f64, + yc as f64, + distance_x as f64, + distance_y as f64, + 0.0, + start_angle, + end_angle, + )?; } else { - ctx.ellipse(xc as f64, yc as f64, distance_x as f64, distance_y as f64, 0.0, start_angle, end_angle + 0.001)?; + ctx.ellipse( + xc as f64, + yc as f64, + distance_x as f64, + distance_y as f64, + 0.0, + start_angle, + end_angle + 0.001, + )?; } - ctx.set_stroke_style(&JsValue::from_str(&format!("{}px {}", gate_width_px + 1.0, color_scheme(state.scope_mode, *value)))); + ctx.set_stroke_style(&JsValue::from_str(&format!( + "{}px {}", + gate_width_px + 1.0, + color_scheme(state.scope_mode, *value) + ))); ctx.stroke(); } @@ -237,4 +374,4 @@ pub fn render(state: &mut ScopeState) -> Result<(), JsValue> { } Ok(()) -} \ No newline at end of file +} diff --git a/nexrad-browser/src/scope.rs b/nexrad-browser/src/scope.rs index 48b39ac..55bbc70 100644 --- a/nexrad-browser/src/scope.rs +++ b/nexrad-browser/src/scope.rs @@ -1,13 +1,26 @@ +use crate::mode::Mode; +use crate::text::OTextArea; +use glyphon::{ + Attrs, Buffer, Family, FontSystem, Metrics, Resolution, Shaping, SwashCache, TextArea, + TextAtlas, TextBounds, TextRenderer, +}; +use nexrad2::Nexrad2Chunk; +use raw_window_handle::HasRawDisplayHandle; +use std::collections::HashMap; use std::iter; +use std::sync::Arc; +use glyphon::fontdb::Source; use web_sys::{CanvasRenderingContext2d, HtmlCanvasElement, HtmlInputElement}; -use wgpu::{Backends, Color, CommandEncoderDescriptor, Device, Features, Instance, InstanceDescriptor, Limits, LoadOp, Operations, Queue, RenderPassColorAttachment, RenderPassDescriptor, StoreOp, Surface, SurfaceConfiguration, SurfaceError, TextureUsages, TextureViewDescriptor}; +use wgpu::DeviceDescriptor; +use wgpu::{ + Backends, Color, CommandEncoderDescriptor, Device, Features, Instance, InstanceDescriptor, + Limits, LoadOp, MultisampleState, Operations, Queue, RenderPassColorAttachment, + RenderPassDescriptor, StoreOp, Surface, SurfaceConfiguration, SurfaceError, TextureUsages, + TextureViewDescriptor, +}; use winit::dpi::PhysicalSize; use winit::event::WindowEvent; -use raw_window_handle::HasRawDisplayHandle; use winit::window::Window; -use nexrad2::Nexrad2Chunk; -use crate::mode::Mode; -use wgpu::DeviceDescriptor; pub struct ScopeState { pub ar2: Option, @@ -20,7 +33,8 @@ pub struct ScopeState { pub command_buf_response_mode: bool, pub new_data_available: bool, pub selected_elevation: usize, - pub show_ui: bool + pub show_ui: bool, + pub wgpu: WgpuState, } pub struct WgpuState { @@ -29,7 +43,12 @@ pub struct WgpuState { pub queue: Queue, pub surface_config: SurfaceConfiguration, pub size: PhysicalSize, - pub window: Window + pub window: Window, + pub font_system: FontSystem, + pub font_cache: SwashCache, + pub atlas: TextAtlas, + pub text_renderer: TextRenderer, + pub text_buffers: HashMap, } impl WgpuState { @@ -40,30 +59,34 @@ impl WgpuState { dx12_shader_compiler: Default::default(), gles_minor_version: Default::default(), }); - let surface = unsafe { - instance.create_surface(&window).unwrap() - }; + let surface = unsafe { instance.create_surface(&window).unwrap() }; - let adapter = instance.request_adapter( - &wgpu::RequestAdapterOptions { + let adapter = instance + .request_adapter(&wgpu::RequestAdapterOptions { power_preference: wgpu::PowerPreference::default(), compatible_surface: Some(&surface), force_fallback_adapter: false, - }, - ).await.unwrap(); + }) + .await + .unwrap(); - let (device, queue) = adapter.request_device( - &DeviceDescriptor { - features: Features::empty(), - limits: Limits::downlevel_webgl2_defaults(), - label: None, - }, - None, // Trace path - ).await.unwrap(); + let (device, queue) = adapter + .request_device( + &DeviceDescriptor { + features: Features::empty(), + limits: Limits::downlevel_webgl2_defaults(), + label: None, + }, + None, // Trace path + ) + .await + .unwrap(); let surface_capabilities = surface.get_capabilities(&adapter); - let surface_format = surface_capabilities.formats.iter() + let surface_format = surface_capabilities + .formats + .iter() .copied() .find(|f| f.is_srgb()) .unwrap_or(surface_capabilities.formats[0]); @@ -80,13 +103,27 @@ impl WgpuState { surface.configure(&device, &surface_config); + let mut fonts = vec![]; + fonts.push(Source::Binary(Arc::new(include_bytes!("./VT323-Regular.ttf")))); + + let mut font_system = FontSystem::new_with_fonts(fonts.into_iter()); + let mut font_cache = SwashCache::new(); + let mut atlas = TextAtlas::new(&device, &queue, surface_format); + let mut text_renderer = + TextRenderer::new(&mut atlas, &device, MultisampleState::default(), None); + Self { window, surface, device, queue, surface_config, - size + size, + font_system, + font_cache, + atlas, + text_renderer, + text_buffers: HashMap::new(), } } @@ -107,29 +144,70 @@ impl WgpuState { false } - pub fn update(&mut self) { - - } + pub fn update(&mut self) {} pub fn render(&mut self) -> Result<(), SurfaceError> { let output = self.surface.get_current_texture()?; - let view = output.texture.create_view(&TextureViewDescriptor::default()); - let mut encoder = self.device.create_command_encoder(&CommandEncoderDescriptor { - label: Some("Render Encoder"), - }); + let view = output + .texture + .create_view(&TextureViewDescriptor::default()); + let mut encoder = self + .device + .create_command_encoder(&CommandEncoderDescriptor { + label: Some("Render Encoder"), + }); + + let physical_width = (self.surface_config.width as f64 * self.window.scale_factor()) as f32; + let physical_height = + (self.surface_config.height as f64 * self.window.scale_factor()) as f32; + + for textarea in self.text_buffers.values_mut() { + // resize the buf + textarea + .buffer + .set_size(&mut self.font_system, physical_width, physical_height); + } + + let textareas = self + .text_buffers + .values() + .map(|u| TextArea { + buffer: &u.buffer, + left: u.left.clone() as f32, + top: u.top.clone() as f32, + scale: u.scale.clone() as f32, + bounds: u.bounds.clone(), + default_color: u.default_color.clone(), + }) + .collect::>(); + + self.text_renderer + .prepare( + &self.device, + &self.queue, + &mut self.font_system, + &mut self.atlas, + Resolution { + width: self.surface_config.width, + height: self.surface_config.height, + }, + textareas, + &mut self.font_cache, + ) + .unwrap(); // LOCK BLOCK { - let render_pass = encoder.begin_render_pass(&RenderPassDescriptor { + let mut render_pass = encoder.begin_render_pass(&RenderPassDescriptor { label: Some("Render Pass"), color_attachments: &[Some(RenderPassColorAttachment { view: &view, resolve_target: None, ops: Operations { load: LoadOp::Clear(Color { - r: 0.1, - g: 0.2, - b: 0.3, + r: 0.0, + g: 0.0, + b: 0.0, a: 1.0, }), store: StoreOp::Store, @@ -139,16 +217,61 @@ impl WgpuState { timestamp_writes: None, occlusion_query_set: None, }); + + self.text_renderer + .render(&self.atlas, &mut render_pass) + .unwrap(); } self.queue.submit(iter::once(encoder.finish())); output.present(); + self.atlas.trim(); + Ok(()) } + + pub fn add_textarea( + &mut self, + id: String, + metrics: Metrics, + left: f64, + top: f64, + scale: f64, + bounds: TextBounds, + default_color: glyphon::Color, + start_text: &str, + ) -> &mut OTextArea { + let mut buffer = Buffer::new(&mut self.font_system, metrics); + + let physical_width = (self.surface_config.width as f64 * self.window.scale_factor()) as f32; + let physical_height = + (self.surface_config.height as f64 * self.window.scale_factor()) as f32; + + buffer.set_size(&mut self.font_system, physical_width, physical_height); + buffer.set_text( + &mut self.font_system, + start_text, + Attrs::new().family(Family::Monospace), + Shaping::Basic, + ); + buffer.shape_until_scroll(&mut self.font_system); + + let textarea = OTextArea { + left, + top, + scale, + bounds, + default_color, + buffer, + }; + + self.text_buffers.insert(id.clone(), textarea); + + self.text_buffers.get_mut(&id).unwrap() + } } - pub struct Preferences { - pub fcs: usize -} \ No newline at end of file + pub fcs: usize, +} diff --git a/nexrad-browser/src/sites.rs b/nexrad-browser/src/sites.rs index 85cf9aa..73be7cf 100644 --- a/nexrad-browser/src/sites.rs +++ b/nexrad-browser/src/sites.rs @@ -6,170 +6,1610 @@ pub struct Site { pub long: f64, pub country: String, pub state: String, - pub place: 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() }) + ( + "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/text.rs b/nexrad-browser/src/text.rs new file mode 100644 index 0000000..6a599ba --- /dev/null +++ b/nexrad-browser/src/text.rs @@ -0,0 +1,10 @@ +use glyphon::{Buffer, Color, TextArea, TextBounds}; + +pub struct OTextArea { + pub left: f64, + pub top: f64, + pub scale: f64, + pub bounds: TextBounds, + pub default_color: Color, + pub buffer: Buffer, +} diff --git a/nexrad-browser/src/utils.rs b/nexrad-browser/src/utils.rs index e45c9e1..48ec662 100644 --- a/nexrad-browser/src/utils.rs +++ b/nexrad-browser/src/utils.rs @@ -18,4 +18,4 @@ pub fn parse_date(julian: u32, millis: u32) -> DateTime { date += Duration::days(julian as i64 - 1); date += Duration::milliseconds(millis as i64); date -} \ No newline at end of file +} diff --git a/nexrad-browser/src/vcp.rs b/nexrad-browser/src/vcp.rs index b9f165c..3f51e96 100644 --- a/nexrad-browser/src/vcp.rs +++ b/nexrad-browser/src/vcp.rs @@ -14,4 +14,4 @@ pub const fn vcp_string(vcp: i16) -> &'static str { } else { "UNKNOWN" } -} \ No newline at end of file +} diff --git a/nexrad-browser/www/VT323-Regular.ttf b/nexrad-browser/www/VT323-Regular.ttf deleted file mode 100644 index e69de29..0000000 diff --git a/nexrad-browser/www/index.js b/nexrad-browser/www/index.js index 71b0035..4b96522 100644 --- a/nexrad-browser/www/index.js +++ b/nexrad-browser/www/index.js @@ -1,6 +1,12 @@ import * as wasm from "./wasm/nexrad_browser.js"; await wasm.default(); -let global_context = wasm.__nxrd_browser_init(window.innerWidth, window.innerHeight); +let abi_proxy = await wasm.__nxrd_browser_init(window.innerWidth, window.innerHeight); + +window.onresize = async () => { + console.log("window resized!"); + await wasm.resize_abi(window.innerWidth, window.innerHeight, abi_proxy); +} + /* function rescaleCanvas(canvas, ctx) { var dpr = window.devicePixelRatio || 1;