fully rewrite browser client, site delay broken
This commit is contained in:
parent
05f24ca21f
commit
a729956898
Binary file not shown.
|
@ -14,20 +14,32 @@ crate-type = ["cdylib", "rlib"]
|
||||||
default = ["console_error_panic_hook"]
|
default = ["console_error_panic_hook"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
wasm-bindgen = "0.2.84"
|
wasm-bindgen = "0.2"
|
||||||
|
console_error_panic_hook = { version = "0.1", optional = true }
|
||||||
# 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 }
|
|
||||||
nexrad2 = { version = "0.1.0", path = "../nexrad2", default-features = false, features = ["bzip-impl-bzip2-rs", "serde_derive"] }
|
nexrad2 = { version = "0.1.0", path = "../nexrad2", default-features = false, features = ["bzip-impl-bzip2-rs", "serde_derive"] }
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
web-sys = "0.3"
|
|
||||||
js-sys = "0.3"
|
|
||||||
wasm-logger = "0.2"
|
wasm-logger = "0.2"
|
||||||
serde-wasm-bindgen = "0.6"
|
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]
|
[dev-dependencies]
|
||||||
wasm-bindgen-test = "0.3.34"
|
wasm-bindgen-test = "0.3"
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
|
@ -1,36 +1,114 @@
|
||||||
pub mod utils;
|
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::io::Cursor;
|
||||||
|
use std::ops::{Deref, DerefMut};
|
||||||
use js_sys::Uint8Array;
|
use js_sys::Uint8Array;
|
||||||
use log::info;
|
use log::info;
|
||||||
use wasm_bindgen::prelude::*;
|
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]
|
#[wasm_bindgen]
|
||||||
extern "C" {
|
extern "C" {
|
||||||
fn alert(s: &str);
|
fn rescaleCanvas(canvas: &HtmlCanvasElement, ctx: &CanvasRenderingContext2d);
|
||||||
|
fn loadar2(c: &str);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[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));
|
wasm_logger::init(wasm_logger::Config::new(log::Level::Debug));
|
||||||
utils::set_panic_hook();
|
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::<web_sys::HtmlCanvasElement>().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::<web_sys::CanvasRenderingContext2d>().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::<web_sys::HtmlInputElement>().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");
|
info!("nexrad-browser initialized successfully");
|
||||||
|
|
||||||
|
AbiScopeState(scope_state)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn load_ar2(buf: &JsValue) -> JsValue {
|
pub fn render_abi(state: &mut AbiScopeState) {
|
||||||
|
rendering::render(&mut state.0).unwrap()
|
||||||
info!("loading archive 2 file");
|
}
|
||||||
info!("load 01: convert to uint8array");
|
|
||||||
let array = Uint8Array::new(buf);
|
#[wasm_bindgen]
|
||||||
info!("load 02: convert to rust vec");
|
pub fn load_ar2(buf: &JsValue, scope: &mut AbiScopeState) {
|
||||||
let rvec = array.to_vec();
|
let array = Uint8Array::new(buf);
|
||||||
info!("load 03: create cursor");
|
let rvec = array.to_vec();
|
||||||
let mut cursor = Cursor::new(rvec);
|
let mut cursor = Cursor::new(rvec);
|
||||||
info!("load 04: load");
|
let loaded = nexrad2::parse_nx2_chunk(&mut cursor).unwrap();
|
||||||
let loaded = nexrad2::parse_nx2_chunk(&mut cursor).unwrap();
|
info!("new data chunk loaded");
|
||||||
info!("load 05: dump");
|
scope.0.ar2 = Some(loaded);
|
||||||
info!("Loaded: {:#?}", loaded);
|
scope.0.new_data_available = false;
|
||||||
serde_wasm_bindgen::to_value(&loaded).unwrap()
|
}
|
||||||
|
|
||||||
|
#[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();
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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<Utc>) -> (usize, usize, usize) {
|
||||||
|
(t.hour() as usize, t.minute() as usize, t.second() as usize)
|
||||||
|
}
|
||||||
|
pub fn zulu(t: DateTime<Utc>) -> 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(())
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
use web_sys::{CanvasRenderingContext2d, HtmlCanvasElement, HtmlInputElement};
|
||||||
|
use nexrad2::Nexrad2Chunk;
|
||||||
|
use crate::mode::Mode;
|
||||||
|
|
||||||
|
pub struct ScopeState {
|
||||||
|
pub ar2: Option<Nexrad2Chunk>,
|
||||||
|
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
|
||||||
|
}
|
|
@ -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<String, Site> {
|
||||||
|
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() })
|
||||||
|
])
|
||||||
|
}
|
|
@ -1,3 +1,5 @@
|
||||||
|
use chrono::{DateTime, Duration, Utc};
|
||||||
|
|
||||||
pub fn set_panic_hook() {
|
pub fn set_panic_hook() {
|
||||||
// When the `console_error_panic_hook` feature is enabled, we can call the
|
// When the `console_error_panic_hook` feature is enabled, we can call the
|
||||||
// `set_panic_hook` function at least once during initialization, and then
|
// `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")]
|
#[cfg(feature = "console_error_panic_hook")]
|
||||||
console_error_panic_hook::set_once();
|
console_error_panic_hook::set_once();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub const MILLIS_PER_DAY: u128 = 86_400_000;
|
||||||
|
|
||||||
|
pub fn parse_date(julian: u32, millis: u32) -> DateTime<Utc> {
|
||||||
|
let unix_time_millis = (julian as u128) * MILLIS_PER_DAY + millis as u128;
|
||||||
|
DateTime::UNIX_EPOCH + Duration::milliseconds(unix_time_millis as i64)
|
||||||
|
}
|
|
@ -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"
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,37 +1,6 @@
|
||||||
import * as wasm from "./wasm/nexrad_browser.js";
|
import * as wasm from "./wasm/nexrad_browser.js";
|
||||||
await wasm.default();
|
await wasm.default();
|
||||||
wasm.__nxrd_browser_init();
|
let global_context = 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
function rescaleCanvas(canvas, ctx) {
|
function rescaleCanvas(canvas, ctx) {
|
||||||
var dpr = window.devicePixelRatio || 1;
|
var dpr = window.devicePixelRatio || 1;
|
||||||
|
@ -45,360 +14,30 @@ function rescaleCanvas(canvas, ctx) {
|
||||||
// don't have to worry about the difference.
|
// don't have to worry about the difference.
|
||||||
ctx.scale(dpr, dpr);
|
ctx.scale(dpr, dpr);
|
||||||
}
|
}
|
||||||
|
window.rescaleCanvas = rescaleCanvas;
|
||||||
|
|
||||||
const ctx = setupCanvas(canvas);
|
window.loadar2 = (s) => {
|
||||||
|
|
||||||
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;
|
|
||||||
const file = document.getElementById("file").files[0];
|
const file = document.getElementById("file").files[0];
|
||||||
document.getElementById("file").value = null;
|
document.getElementById("file").value = null;
|
||||||
const reader = new FileReader();
|
const reader = new FileReader();
|
||||||
reader.addEventListener('load', (event) => {
|
reader.addEventListener('load', (event) => {
|
||||||
let data = event.target.result;
|
let data = event.target.result;
|
||||||
let loaded = wasm.load_ar2(data);
|
wasm.load_ar2(data, global_context);
|
||||||
console.log(loaded);
|
|
||||||
cmd_err("");
|
|
||||||
|
|
||||||
ar2 = loaded;
|
|
||||||
data_status = 1;
|
|
||||||
});
|
});
|
||||||
reader.readAsArrayBuffer(file);
|
reader.readAsArrayBuffer(file);
|
||||||
|
console.log(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
function exec(command) {
|
// END GLOBAL FUNCTION DEFS
|
||||||
console.log("exec1!");
|
|
||||||
|
|
||||||
let tokens = command.split(" ");
|
window.onkeydown = (e) => {
|
||||||
|
wasm.keydown(global_context, e.key);
|
||||||
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").onchange = () => {
|
||||||
document.getElementById("file").click();
|
wasm.new_file_available(global_context);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tokens[0] === "CLF" && tokens[1] === "RELOAD") {
|
let render = () => { wasm.render_abi(global_context); requestAnimationFrame(render); }
|
||||||
// TRIGGER RELOAD
|
|
||||||
cmd_err("SYSTEM PROCESSING");
|
|
||||||
load();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (command === "TEST UNSET INOP") {
|
requestAnimationFrame(render);
|
||||||
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");
|
|
||||||
}
|
|
||||||
|
|
||||||
function shouldNewline() {
|
|
||||||
if (command_buf.startsWith("MODE SET")) { return true; }
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
let ar2_date = undefined;
|
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
|
|
@ -6,34 +6,34 @@ pub const MSG_RDA_STATUS_DATA: u8 = 2;
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
pub struct Msg02RDAStatusData {
|
pub struct Msg02RDAStatusData {
|
||||||
rda_status: u16,
|
pub rda_status: u16,
|
||||||
operability_status: u16,
|
pub operability_status: u16,
|
||||||
control_status: u16,
|
pub control_status: u16,
|
||||||
aux_power_state: u16,
|
pub aux_power_state: u16,
|
||||||
average_transmitter_power_watts: u16,
|
pub average_transmitter_power_watts: u16,
|
||||||
horiz_ref_calib_corr: u16,
|
pub horiz_ref_calib_corr: u16,
|
||||||
data_tx_enabled: u16,
|
pub data_tx_enabled: u16,
|
||||||
vcp: i16,
|
pub vcp: i16,
|
||||||
rdaca: u16,
|
pub rdaca: u16,
|
||||||
rda_build_no: u16,
|
pub rda_build_no: u16,
|
||||||
opmode: u16,
|
pub opmode: u16,
|
||||||
super_resolution_status: u16,
|
pub super_resolution_status: u16,
|
||||||
clutter_mitigation_status: u16,
|
pub clutter_mitigation_status: u16,
|
||||||
rda_scan_data_flags: u16,
|
pub rda_scan_data_flags: u16,
|
||||||
rda_alarm_summary: u16,
|
pub rda_alarm_summary: u16,
|
||||||
command_ack: u16,
|
pub command_ack: u16,
|
||||||
channel_con_stat: u16,
|
pub channel_con_stat: u16,
|
||||||
spot_blank_stat: u16,
|
pub spot_blank_stat: u16,
|
||||||
bypass_map_gen_date: u16,
|
pub bypass_map_gen_date: u16,
|
||||||
bypass_map_gen_time: u16,
|
pub bypass_map_gen_time: u16,
|
||||||
clutter_map_gen_date: u16,
|
pub clutter_map_gen_date: u16,
|
||||||
clutter_map_gen_time: u16,
|
pub clutter_map_gen_time: u16,
|
||||||
vert_ref_calib_corr: u16,
|
pub vert_ref_calib_corr: u16,
|
||||||
tps_status: u16,
|
pub tps_status: u16,
|
||||||
rms_control_status: u16,
|
pub rms_control_status: u16,
|
||||||
perf_check_status: u16,
|
pub perf_check_status: u16,
|
||||||
alarm: u16,
|
pub alarm: u16,
|
||||||
extra: [u8; 8],
|
pub extra: [u8; 8],
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromBody<68> for Msg02RDAStatusData {
|
impl FromBody<68> for Msg02RDAStatusData {
|
||||||
|
|
|
@ -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;
|
pub const MSG31_HEADER_LENGTH: usize = 4 + 4 + 2 + 2 + 4 + 1 + 1 + 2 + 1 + 1 + 1 + 1 + 4 + 1 + 1 + 2;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
#[cfg_attr(feature = "serde_derive", derive(serde::Serialize, serde::Deserialize))]
|
||||||
pub struct Message31Header {
|
pub struct Message31Header {
|
||||||
pub radar_identifier: String,
|
pub radar_identifier: String,
|
||||||
pub collection_time: u32,
|
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;
|
pub const VOLUME_DATA_LENGTH: usize = 1 + 3 + 2 + 1 + 1 + 4 + 4 + 2 + 2 + 4 + 4 + 4 + 4 + 4 + 2 + 2;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
#[cfg_attr(feature = "serde_derive", derive(serde::Serialize, serde::Deserialize))]
|
||||||
pub struct VolumeData {
|
pub struct VolumeData {
|
||||||
pub datablock_type: String,
|
pub datablock_type: String,
|
||||||
pub data_name: String,
|
pub data_name: String,
|
||||||
|
@ -48,6 +50,7 @@ pub struct VolumeData {
|
||||||
pub const ELEVATION_DATA_LENGTH: usize = 1 + 3 + 2 + 2 + 4;
|
pub const ELEVATION_DATA_LENGTH: usize = 1 + 3 + 2 + 2 + 4;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
#[cfg_attr(feature = "serde_derive", derive(serde::Serialize, serde::Deserialize))]
|
||||||
pub struct ElevationData {
|
pub struct ElevationData {
|
||||||
pub datablock_type: String,
|
pub datablock_type: String,
|
||||||
pub data_name: 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;
|
pub const RADIAL_DATA_LENGTH: usize = 1 + 3 + 2 + 2 + 4 + 4 + 2 + 2 + 4 + 4;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
#[cfg_attr(feature = "serde_derive", derive(serde::Serialize, serde::Deserialize))]
|
||||||
pub struct RadialData {
|
pub struct RadialData {
|
||||||
pub datablock_type: String,
|
pub datablock_type: String,
|
||||||
pub data_name: 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;
|
pub const GENERIC_DATA_MOMENT_LEN: usize = 1 + 3 + 4 + 2 + 2 + 2 + 2 + 2 + 1 + 1 + 4 + 4;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
#[cfg_attr(feature = "serde_derive", derive(serde::Serialize, serde::Deserialize))]
|
||||||
pub struct GenericDataMoment {
|
pub struct GenericDataMoment {
|
||||||
pub datablock_type: String,
|
pub datablock_type: String,
|
||||||
pub data_name: String,
|
pub data_name: String,
|
||||||
|
@ -90,6 +95,7 @@ pub struct GenericDataMoment {
|
||||||
pub offset: f32
|
pub offset: f32
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(feature = "serde_derive", derive(serde::Serialize, serde::Deserialize))]
|
||||||
pub struct DataMoment {
|
pub struct DataMoment {
|
||||||
pub gdm: GenericDataMoment,
|
pub gdm: GenericDataMoment,
|
||||||
pub data: Vec<u8>
|
pub data: Vec<u8>
|
||||||
|
@ -102,6 +108,7 @@ impl Debug for DataMoment {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
#[cfg_attr(feature = "serde_derive", derive(serde::Serialize, serde::Deserialize))]
|
||||||
pub struct Message31 {
|
pub struct Message31 {
|
||||||
pub header: Message31Header,
|
pub header: Message31Header,
|
||||||
pub volume_info: Option<VolumeData>,
|
pub volume_info: Option<VolumeData>,
|
||||||
|
|
Loading…
Reference in New Issue