single site radar

This commit is contained in:
core 2025-04-04 20:42:39 -04:00
parent 72cb16ac3f
commit 97dc8866c2
18 changed files with 1022 additions and 3 deletions

2
.idea/wxbox.iml generated
View file

@ -17,6 +17,8 @@
<sourceFolder url="file://$MODULE_DIR$/crates/grib2/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/crates/pal/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/crates/tiler/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/crates/ar2/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/crates/nommer/src" isTestSource="false" />
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
<excludeFolder url="file://$MODULE_DIR$/temp" />
<excludeFolder url="file://$MODULE_DIR$/tmp" />

257
Cargo.lock generated
View file

@ -191,6 +191,12 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04"
[[package]]
name = "android-tzdata"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
[[package]]
name = "android_system_properties"
version = "0.1.5"
@ -200,6 +206,56 @@ dependencies = [
"libc",
]
[[package]]
name = "anstream"
version = "0.6.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b"
dependencies = [
"anstyle",
"anstyle-parse",
"anstyle-query",
"anstyle-wincon",
"colorchoice",
"is_terminal_polyfill",
"utf8parse",
]
[[package]]
name = "anstyle"
version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9"
[[package]]
name = "anstyle-parse"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9"
dependencies = [
"utf8parse",
]
[[package]]
name = "anstyle-query"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c"
dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "anstyle-wincon"
version = "3.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e"
dependencies = [
"anstyle",
"once_cell",
"windows-sys 0.59.0",
]
[[package]]
name = "anyhow"
version = "1.0.97"
@ -719,6 +775,26 @@ version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
[[package]]
name = "bzip2"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bdb116a6ef3f6c3698828873ad02c3014b3c85cadb88496095628e3ef1e347f8"
dependencies = [
"bzip2-sys",
"libc",
]
[[package]]
name = "bzip2-sys"
version = "0.1.13+1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "225bff33b2141874fe80d71e07d6eec4f85c5c216453dd96388240f96e1acc14"
dependencies = [
"cc",
"pkg-config",
]
[[package]]
name = "cacache"
version = "13.1.0"
@ -820,6 +896,60 @@ dependencies = [
"libc",
]
[[package]]
name = "chrono"
version = "0.4.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c"
dependencies = [
"android-tzdata",
"iana-time-zone",
"js-sys",
"num-traits",
"wasm-bindgen",
"windows-link",
]
[[package]]
name = "clap"
version = "4.5.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8aa86934b44c19c50f87cc2790e19f54f7a67aedb64101c2e1a2e5ecfb73944"
dependencies = [
"clap_builder",
"clap_derive",
]
[[package]]
name = "clap_builder"
version = "4.5.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2414dbb2dd0695280da6ea9261e327479e9d37b0630f6b53ba2a11c60c679fd9"
dependencies = [
"anstream",
"anstyle",
"clap_lex",
"strsim",
]
[[package]]
name = "clap_derive"
version = "4.5.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "clap_lex"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6"
[[package]]
name = "clipboard-win"
version = "5.4.0"
@ -851,6 +981,12 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
[[package]]
name = "colorchoice"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
[[package]]
name = "combine"
version = "4.6.7"
@ -2067,6 +2203,30 @@ dependencies = [
"tracing",
]
[[package]]
name = "iana-time-zone"
version = "0.1.63"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8"
dependencies = [
"android_system_properties",
"core-foundation-sys",
"iana-time-zone-haiku",
"js-sys",
"log",
"wasm-bindgen",
"windows-core 0.61.0",
]
[[package]]
name = "iana-time-zone-haiku"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
dependencies = [
"cc",
]
[[package]]
name = "icu_collections"
version = "1.5.0"
@ -2287,6 +2447,12 @@ version = "2.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130"
[[package]]
name = "is_terminal_polyfill"
version = "1.70.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
[[package]]
name = "itertools"
version = "0.12.1"
@ -2755,6 +2921,50 @@ version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086"
[[package]]
name = "nexrad-data"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b2aef96f687e5774386f0dfe4e95bbf98b531559426e4b3bdddd27ca3d38488"
dependencies = [
"bincode",
"bzip2",
"chrono",
"clap",
"log",
"nexrad-decode",
"nexrad-model",
"reqwest",
"serde",
"thiserror 1.0.69",
"tokio",
"xml",
]
[[package]]
name = "nexrad-decode"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dab458c09a15d9a133a7935a8024022db3cd3282549c2ed000f44c4ea392213a"
dependencies = [
"bincode",
"chrono",
"log",
"nexrad-model",
"serde",
"thiserror 1.0.69",
"uom",
]
[[package]]
name = "nexrad-model"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a13fa673733e34220daf6f2ac75051d94d66acdd3fd2127f76593b6a36d1593c"
dependencies = [
"thiserror 1.0.69",
]
[[package]]
name = "nix"
version = "0.29.0"
@ -4316,6 +4526,12 @@ dependencies = [
"float-cmp",
]
[[package]]
name = "strsim"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "strum"
version = "0.26.3"
@ -4886,6 +5102,16 @@ version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
[[package]]
name = "uom"
version = "0.36.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffd36e5350a65d112584053ee91843955826bf9e56ec0d1351214e01f6d7cd9c"
dependencies = [
"num-traits",
"typenum",
]
[[package]]
name = "url"
version = "2.5.4"
@ -4954,6 +5180,12 @@ version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
[[package]]
name = "utf8parse"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "uuid"
version = "1.16.0"
@ -5977,6 +6209,16 @@ version = "0.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51"
[[package]]
name = "wxbox-ar2"
version = "0.1.0"
dependencies = [
"nexrad-data",
"nexrad-decode",
"serde",
"toml",
]
[[package]]
name = "wxbox-client"
version = "0.1.0"
@ -6018,8 +6260,13 @@ dependencies = [
"png",
"thiserror 2.0.12",
"tracing",
"wxbox-nommer",
]
[[package]]
name = "wxbox-nommer"
version = "0.1.0"
[[package]]
name = "wxbox-pal"
version = "0.1.0"
@ -6043,6 +6290,7 @@ dependencies = [
"toml",
"tracing",
"tracing-subscriber",
"wxbox-ar2",
"wxbox-grib2",
"wxbox-pal",
]
@ -6114,6 +6362,15 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56"
[[package]]
name = "xml"
version = "0.8.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ede1c99c55b4b3ad0349018ef0eccbe954ce9c342334410707ee87177fcf2ab4"
dependencies = [
"xml-rs",
]
[[package]]
name = "xml-rs"
version = "0.8.25"

10
crates/ar2/Cargo.toml Normal file
View file

@ -0,0 +1,10 @@
[package]
name = "wxbox-ar2"
version = "0.1.0"
edition = "2024"
[dependencies]
nexrad-decode = "0.1.1"
nexrad-data = "0.2"
serde = { version = "1", features = ["derive"]}
toml = "0.8"

Binary file not shown.

194
crates/ar2/src/lib.rs Normal file
View file

@ -0,0 +1,194 @@
use std::fmt::Debug;
use nexrad_data::volume::File;
use nexrad_decode::messages::digital_radar_data::{GenericDataBlock, RadialStatus};
use nexrad_decode::messages::MessageContents;
use nexrad_decode::result::Error;
pub mod sites;
pub struct Scan {
pub coverage_pattern_number: u16,
pub sweeps: Vec<Sweep>,
}
pub struct Sweep {
pub elevation_number: u8,
pub radials: Vec<Radial>,
}
pub struct Radial {
pub collection_timestamp: i64,
pub azimuth_number: u16,
pub azimuth_angle_degrees: f32,
pub azimuth_spacing_degrees: f32,
pub radial_status: RadialStatus,
pub elevation_number: u8,
pub elevation_number_degrees: f32,
pub reflectivity: Option<MomentData>,
pub velocity: Option<MomentData>,
pub spectrum_width: Option<MomentData>,
pub differential_reflectivity: Option<MomentData>,
pub differential_phase: Option<MomentData>,
pub correlation_coefficient: Option<MomentData>,
pub specific_differential_phase: Option<MomentData>,
}
#[derive(Debug)]
pub struct MomentData {
pub scale: f32,
pub offset: f32,
pub values: Vec<u8>,
pub start_range: u16,
pub sample_interval: u16
}
impl MomentData {
/// Values from this data moment corresponding to gates in the radial.
pub fn values(&self) -> Vec<MomentValue> {
let copied_values = self.values.iter().copied();
if self.scale == 0.0 {
return copied_values
.map(|raw_value| MomentValue::Value(raw_value as f32))
.collect();
}
copied_values
.map(|raw_value| match raw_value {
0 => MomentValue::BelowThreshold,
1 => MomentValue::RangeFolded,
_ => MomentValue::Value((raw_value as f32 - self.offset) / self.scale),
})
.collect()
}
}
/// The data moment value for a product in a radial's gate. The value may be a floating-point number
/// or a special case such as "below threshold" or "range folded".
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum MomentValue {
/// The data moment value for a gate.
Value(f32),
/// The value for this gate was below the signal threshold.
BelowThreshold,
/// The value for this gate exceeded the maximum unambiguous range.
RangeFolded,
}
pub fn parse(input: Vec<u8>) -> nexrad_data::result::Result<Scan> {
let file = File::new(input);
let mut vcp = None;
let mut radials = vec![];
for mut record in file.records() {
if record.compressed() {
record = record.decompress()?;
}
let messages = record.messages()?;
for message in messages {
let contents = message.into_contents();
if let MessageContents::DigitalRadarData(radar_data_message) = contents {
if vcp.is_none() {
if let Some(volume_block) = &radar_data_message.volume_data_block {
vcp =
Some(volume_block.volume_coverage_pattern_number);
}
}
radials.push(into_radial(*radar_data_message)?);
}
}
}
Ok(Scan {
coverage_pattern_number: vcp.ok_or(Error::DecodingError("no vcp".to_string()))?,
sweeps: Sweep::from_radials(radials),
})
}
fn into_radial(message: nexrad_decode::messages::digital_radar_data::Message) -> nexrad_data::result::Result<Radial> {
Ok(Radial {
collection_timestamp: message.header.date_time().ok_or(Error::MessageMissingDateError)?.timestamp_millis(),
azimuth_number: message.header.azimuth_number,
azimuth_angle_degrees: message.header.azimuth_angle,
azimuth_spacing_degrees: message.header.azimuth_resolution_spacing as f32 * 0.5,
radial_status: message.header.radial_status(),
elevation_number: message.header.elevation_number,
elevation_number_degrees: message.header.elevation_angle,
reflectivity: message.reflectivity_data_block.map(|u| into_moment_data(u)),
velocity: message.velocity_data_block.map(|u| into_moment_data(u)),
spectrum_width: message.spectrum_width_data_block.map(|u| into_moment_data(u)),
differential_reflectivity: message.differential_reflectivity_data_block.map(|u| into_moment_data(u)),
differential_phase: message.differential_phase_data_block.map(|u| into_moment_data(u)),
correlation_coefficient: message.correlation_coefficient_data_block.map(|u| into_moment_data(u)),
specific_differential_phase: message.specific_diff_phase_data_block.map(|u| into_moment_data(u)),
})
}
fn into_moment_data(block: GenericDataBlock) -> MomentData {
MomentData {
scale: block.header.scale,
offset: block.header.offset,
values: block.encoded_data,
start_range: block.header.data_moment_range,
sample_interval: block.header.data_moment_range_sample_interval
}
}
impl Sweep {
pub fn new(elevation_number: u8, radials: Vec<Radial>) -> Self {
Self {
elevation_number,
radials,
}
}
pub fn from_radials(radials: Vec<Radial>) -> Vec<Self> {
let mut sweeps = Vec::new();
let mut sweep_elevation_number = None;
let mut sweep_radials = Vec::new();
for radial in radials {
if let Some(elevation_number) = sweep_elevation_number {
if elevation_number != radial.elevation_number {
sweeps.push(Sweep::new(elevation_number, sweep_radials));
sweep_radials = Vec::new();
}
}
sweep_elevation_number = Some(radial.elevation_number);
sweep_radials.push(radial);
}
sweeps
}
}
pub const DATA_BYTES: &[u8] = include_bytes!("../KCRP20170825_235733_V06");

8
crates/ar2/src/main.rs Normal file
View file

@ -0,0 +1,8 @@
use std::fs;
use wxbox_ar2::parse;
fn main() {
let f = fs::read("KCRP20170825_235733_V06").unwrap();
let f = parse(f).unwrap();
println!("{:?}", f.sweeps.get(0).unwrap().radials.get(0).unwrap().reflectivity.as_ref().unwrap());
}

View file

@ -0,0 +1 @@
pub mod wsr88d;

View file

@ -0,0 +1,114 @@
use serde::{Deserialize, Deserializer};
use std::collections::HashMap;
use std::sync::{LazyLock, OnceLock};
#[derive(Deserialize, Debug)]
pub struct Wsr88dSite {
pub id: String,
pub name: String,
pub agency: String,
pub equipment: String,
pub city: String,
pub state: String,
pub county: String,
#[serde(deserialize_with = "from_elevation_str")]
pub elevation: f64,
#[serde(deserialize_with = "from_deg_str")]
pub lat: f64,
#[serde(deserialize_with = "from_deg_str")]
pub long: f64,
}
#[derive(Deserialize)]
pub struct Wsr88dSites {
pub sites: HashMap<String, Wsr88dSite>,
}
fn from_elevation_str<'de, D>(deserializer: D) -> Result<f64, D::Error>
where
D: Deserializer<'de>,
{
let s: String = Deserialize::deserialize(deserializer)?;
let elevation = s.split(" ").nth(0).unwrap();
Ok(elevation.parse().unwrap())
}
fn from_deg_str<'de, D>(deserializer: D) -> Result<f64, D::Error>
where
D: Deserializer<'de>,
{
let s: String = Deserialize::deserialize(deserializer)?;
let mut split = s.split(" ");
let mut hr = split.next().unwrap();
let positive = hr.starts_with('+');
if positive {
hr = hr.strip_prefix('+').unwrap();
} else {
hr = hr.strip_prefix('-').unwrap();
}
let hr: f64 = hr.parse().unwrap();
let min = split.next().unwrap();
let min: f64 = min.parse().unwrap();
let sec = split.next().unwrap();
let sec: f64 = sec.parse().unwrap();
let mut degrees = hr + min / 60.0 + sec / 3600.0;
if !positive {
degrees *= -1.0;
}
Ok(degrees)
}
pub static SITES: LazyLock<Wsr88dSites> =
LazyLock::new(|| toml::from_str(include_str!("wsr88d.toml")).unwrap());
#[cfg(test)]
mod tests {
use crate::sites::wsr88d::SITES;
const A: f64 = 6_378.1370 * 1000.0; // m
const B: f64 = 6_356.7523 * 1000.0; // m
#[test]
fn azimuth_configurations() {
let radar = SITES.sites.get("KCRP").unwrap();
let radar_theta = radar.long;
let radar_phi = radar.lat;
let radar_r = ((A.powi(2) * radar_phi.cos()).powi(2) + (B.powi(2) * radar_phi.sin()).powi(2)
/ (A * radar_phi.cos()).powi(2) + (B * radar_phi.sin()).powi(2)).sqrt();
let radar_x = radar_r * radar_theta.cos() * radar_phi.sin();
let radar_y = radar_r * radar_theta.sin() * radar_phi.sin();
let radar_z = radar_r * radar_theta.cos();
let measurement_theta = radar_theta + 0.0001;
let measurement_phi = radar_phi;
let measurement_r = radar_r;
let measurement_x = measurement_r * measurement_theta.cos() * measurement_phi.sin();
let measurement_y = measurement_r * measurement_theta.sin() * measurement_phi.sin();
let measurement_z = measurement_r * measurement_theta.cos();
let radar_local_x = measurement_x - radar_x;
let radar_local_y = measurement_y - radar_y;
let radar_local_z = measurement_z - radar_z;
let radar_local_r = (radar_local_x.powi(2) + radar_local_y.powi(2) + radar_local_z.powi(2)).sqrt();
let radar_local_theta = (radar_local_y / radar_local_x).atan();
let radar_local_phi = (radar_local_z / radar_local_r).acos();
let azimuth = radar_local_theta;
let elevation = radar_local_phi;
let distance = radar_local_r;
}
}

View file

@ -0,0 +1,161 @@
[sites]
KABR = {id = "ABR", name = "ABERDEEN", agency = "NWS", equipment = "RDA", city = "ABERDEEN", state = "SD", county = "BROWN", elevation = "396.85 m (1299.21 ft)", lat = "+45 27 21", long = "-98 24 48"}
KENX = {id = "ENX", name = "ALBANY", agency = "NWS", equipment = "RDA", city = "EAST BERNE", state = "NY", county = "ALBANY", elevation = "565 m (1853.67 ft)", lat = "+42 35 11.6", long = "-74 03 50.7"}
KABX = {id = "ABX", name = "ALBUQUERQUE", agency = "NWS", equipment = "RDA", city = "ALBUQUERQUE", state = "NM", county = "BERNALILLO", elevation = "1789.18 m (5869.42 ft)", lat = "+35 08 59", long = "-106 49 26"}
KFDR = {id = "FDR", name = "ALTUS AFB", agency = "AFWA", equipment = "RDA", city = "FREDERICK", state = "OK", county = "TILLMAN", elevation = "386.18 m (1266.4 ft)", lat = "+34 21 43.9", long = "-98 58 36"}
KAMA = {id = "AMA", name = "AMARILLO", agency = "NWS", equipment = "RDA", city = "AMARILLO", state = "TX", county = "POTTER", elevation = "1104 m (3622.05 ft)", lat = "+35 14 00", long = "-101 42 33.4"}
PAHG = {id = "AHG", name = "KENAI FAA (RDA 1)", agency = "FAA", equipment = "RDA", city = "KENAI", state = "AK", county = "N/A", elevation = "73.76 m (239.5 ft)", lat = "+60 43 33.29", long = "-151 21 05.28"}
PGUA = {id = "UAM", name = "ANDERSEN AFB", agency = "AFWA", equipment = "RDA", city = "ANDERSEN AFB", state = "GU", county = "N/A", elevation = "83 m (272.31 ft)", lat = "+13 27 21", long = "+144 48 40"}
KFFC = {id = "FFC", name = "ATLANTA", agency = "NWS", equipment = "RDA", city = "PEACHTREE CITY", state = "GA", county = "FAYETTE", elevation = "261.52 m (856.3 ft)", lat = "+33 21 48.78", long = "-84 33 57.42"}
KEWX = {id = "EWX", name = "AUSTIN/SAN ANTONIO", agency = "NWS", equipment = "RDA", city = "NEW BRAUNFELS", state = "TX", county = "COMAL", elevation = "204 m (669.29 ft)", lat = "+29 42 14.6", long = "-98 01 43"}
KBBX = {id = "BBX", name = "BEALE AFB", agency = "AFWA", equipment = "RDA", city = "OROVILLE", state = "CA", county = "BUTTE", elevation = "52.73 m (170.6 ft)", lat = "+39 29 44.3", long = "-121 37 53.8"}
PABC = {id = "ABC", name = "BETHEL FAA (RDA 1)", agency = "FAA", equipment = "RDA", city = "BETHEL", state = "AK", county = "N/A", elevation = "49.07 m (160.76 ft)", lat = "+60 47 31", long = "-161 52 35"}
KBLX = {id = "BLX", name = "BILLINGS", agency = "NWS", equipment = "RDA", city = "BILLINGS", state = "MT", county = "YELLOWSTONE", elevation = "1109 m (3638.45 ft)", lat = "+45 51 13.6", long = "-108 36 24.5"}
KBGM = {id = "BGM", name = "BINGHAMTON", agency = "NWS", equipment = "RDA", city = "BINGHAMTON", state = "NY", county = "BROOME", elevation = "489.51 m (1604.33 ft)", lat = "+42 11 58.9", long = "-75 59 05"}
KBMX = {id = "BMX", name = "BIRMINGHAM", agency = "NWS", equipment = "RDA", city = "ALABASTER", state = "AL", county = "SHELBY", elevation = "196.6 m (643.04 ft)", lat = "+33 10 20.7", long = "-86 46 12.6"}
KBIS = {id = "BIS", name = "BISMARCK", agency = "NWS", equipment = "RDA", city = "BISMARCK", state = "ND", county = "BURLEIGH", elevation = "505.36 m (1656.82 ft)", lat = "+46 46 15", long = "-100 45 38"}
KCBX = {id = "CBX", name = "BOISE", agency = "NWS", equipment = "RDA", city = "BOISE", state = "ID", county = "ADA", elevation = "942 m (3090.55 ft)", lat = "+43 29 24.78", long = "-116 14 09.72"}
KBOX = {id = "BOX", name = "BOSTON", agency = "NWS", equipment = "RDA", city = "TAUNTON", state = "MA", county = "BRISTOL", elevation = "35.97 m (114.83 ft)", lat = "+41 57 20.8", long = "-71 08 12.7"}
KOKX = {id = "OKX", name = "BROOKHAVEN", agency = "NWS", equipment = "RDA", city = "UPTON", state = "NY", county = "SUFFOLK", elevation = "25.91 m (82.02 ft)", lat = "+40 51 55.9", long = "-72 51 50.1"}
KBRO = {id = "BRO", name = "BROWNSVILLE", agency = "NWS", equipment = "RDA", city = "BROWNSVILLE", state = "TX", county = "CAMERON", elevation = "7.01 m (22.97 ft)", lat = "+25 54 57.6", long = "-97 25 08.28"}
KBUF = {id = "BUF", name = "BUFFALO", agency = "NWS", equipment = "RDA", city = "BUFFALO", state = "NY", county = "ERIE", elevation = "211.23 m (692.26 ft)", lat = "+42 56 55.64", long = "-78 44 12.41"}
KCXX = {id = "CXX", name = "BURLINGTON", agency = "NWS", equipment = "RDA", city = "COLCHESTER", state = "VT", county = "CHITTENDEN", elevation = "96.62 m (314.96 ft)", lat = "+44 30 39.6", long = "-73 09 59.15"}
RKSG = {id = "KSGR4", name = "CAMP HUMPHREYS", agency = "AFWA", equipment = "RDA", city = "CAMP HUMPHREYS", state = "KO", county = "N/A", elevation = "439 m (1440.29 ft)", lat = "+37 12 27.25", long = "+127 17 08.02"}
KFDX = {id = "FDX", name = "CANNON AFB", agency = "AFWA", equipment = "RDA", city = "FIELD", state = "NM", county = "CURRY", elevation = "1417.32 m (4648.95 ft)", lat = "+34 38 03", long = "-103 37 08"}
KCBW = {id = "CBW", name = "CARIBOU", agency = "NWS", equipment = "RDA", city = "HOULTON", state = "ME", county = "AROOSTOOK", elevation = "227.38 m (744.75 ft)", lat = "+46 02 21.30", long = "-67 48 23.15"}
KICX = {id = "ICX", name = "CEDAR CITY (RDA 1)", agency = "NWS", equipment = "RDA", city = "CEDAR CITY", state = "UT", county = "IRON", elevation = "3244 m (10643.04 ft)", lat = "+37 35 27.78", long = "-112 51 43.86"}
KCLX = {id = "CLX", name = "CHARLESTON, SC", agency = "NWS", equipment = "RDA", city = "GRAYS", state = "SC", county = "BEAUFORT", elevation = "35 m (114.83 ft)", lat = "+32 39 19.9", long = "-81 02 31.9"}
KRLX = {id = "RLX", name = "CHARLESTON, WV", agency = "NWS", equipment = "RDA", city = "CHARLESTON", state = "WV", county = "KANAWHA", elevation = "335 m (1099.08 ft)", lat = "+38 18 40", long = "-81 43 22"}
KCYS = {id = "CYS", name = "CHEYENNE", agency = "NWS", equipment = "RDA", city = "CHEYENNE", state = "WY", county = "LARAMIE", elevation = "1867.81 m (6125.33 ft)", lat = "+41 09 06.91", long = "-104 48 21.71"}
KLOT = {id = "LOT", name = "CHICAGO", agency = "NWS", equipment = "RDA", city = "ROMEOVILLE", state = "IL", county = "WILL", elevation = "202.08 m (662.73 ft)", lat = "+41 36 16", long = "-88 05 04"}
KILN = {id = "ILN", name = "CINCINNATI", agency = "NWS", equipment = "RDA", city = "WILMINGTON", state = "OH", county = "CLINTON", elevation = "321.87 m (1053.15 ft)", lat = "+39 25 13.74", long = "-83 49 17.22"}
KCLE = {id = "CLE", name = "CLEVELAND", agency = "NWS", equipment = "RDA", city = "CLEVELAND", state = "OH", county = "CUYAHOGA", elevation = "232.56 m (761.15 ft)", lat = "+41 24 47.58", long = "-81 51 35.52"}
KCAE = {id = "CAE", name = "COLUMBIA", agency = "NWS", equipment = "RDA", city = "WEST COLUMBIA", state = "SC", county = "LEXINGTON", elevation = "70.41 m (229.66 ft)", lat = "+33 56 55.4", long = "-81 07 05.8"}
KGWX = {id = "GWX", name = "COLUMBUS AFB", agency = "AFWA", equipment = "RDA", city = "GREENWOOD SPRINGS", state = "MS", county = "MONROE", elevation = "155 m (508.53 ft)", lat = "+33 53 48.9", long = "-88 19 45.1"}
KCRP = {id = "CRP", name = "CORPUS CHRISTI", agency = "NWS", equipment = "RDA", city = "CORPUS CHRISTI", state = "TX", county = "NUECES", elevation = "13.72 m (42.65 ft)", lat = "+27 47 02.46", long = "-97 30 40.5"}
KFWS = {id = "FWS", name = "DALLAS/FT WORTH", agency = "NWS", equipment = "RDA", city = "FORT WORTH", state = "TX", county = "TARRANT", elevation = "212 m (695.54 ft)", lat = "+32 34 22.8", long = "-97 18 11.34"}
KFTG = {id = "FTG", name = "DENVER", agency = "NWS", equipment = "RDA", city = "FRONT RANGE AP", state = "CO", county = "ARAPAHOE", elevation = "1675.49 m (5495.41 ft)", lat = "+39 47 11.9", long = "-104 32 44.9"}
KDMX = {id = "DMX", name = "DES MOINES", agency = "NWS", equipment = "RDA", city = "JOHNSTON", state = "IA", county = "POLK", elevation = "299.01 m (980.97 ft)", lat = "+41 43 52.32", long = "-93 43 22.33"}
KDTX = {id = "DTX", name = "DETROIT", agency = "NWS", equipment = "RDA", city = "WHITE LAKE", state = "MI", county = "OAKLAND", elevation = "336 m (1102.36 ft)", lat = "+42 42 00", long = "-83 28 18"}
KDDC = {id = "DDC", name = "DODGE CITY", agency = "NWS", equipment = "RDA", city = "DODGE CITY", state = "KS", county = "FORD", elevation = "789.43 m (2588.58 ft)", lat = "+37 45 39", long = "-99 58 08"}
KDOX = {id = "DOX", name = "DOVER AFB", agency = "AFWA", equipment = "RDA", city = "ELLENDALE STATE FOREST", state = "DE", county = "SUSSEX", elevation = "15.24 m (49.21 ft)", lat = "+38 49 32.76", long = "-75 26 24.42"}
KDLH = {id = "DLH", name = "DULUTH", agency = "NWS", equipment = "RDA", city = "DULUTH", state = "MN", county = "ST LOUIS", elevation = "435.25 m (1427.17 ft)", lat = "+46 50 13", long = "-92 12 35"}
KDYX = {id = "DYX", name = "DYESS AFB", agency = "AFWA", equipment = "RDA", city = "MORAN", state = "TX", county = "SHACKELFORD", elevation = "462.38 m (1515.75 ft)", lat = "+32 32 18.6", long = "-99 15 15.6"}
KEYX = {id = "EYX", name = "EDWARDS AFB", agency = "AFWA", equipment = "RDA", city = "BORON", state = "CA", county = "SAN BERNADINO", elevation = "846 m (2775.59 ft)", lat = "+35 05 52.26", long = "-117 33 38.7"}
KEVX = {id = "EVX", name = "EGLIN AFB", agency = "AFWA", equipment = "RDA", city = "RED BAY", state = "FL", county = "WALTON", elevation = "42.67 m (137.8 ft)", lat = "+30 33 54.12", long = "-85 55 18"}
KEPZ = {id = "EPZ", name = "EL PASO", agency = "NWS", equipment = "RDA", city = "SANTA TERESA", state = "NM", county = "DONA ANA", elevation = "1250.9 m (4101.05 ft)", lat = "+31 52 23", long = "-106 41 52.8"}
KLRX = {id = "LRX", name = "ELKO (RDA 1)", agency = "NWS", equipment = "RDA", city = "ELKO", state = "NV", county = "LANDER", elevation = "2067 m (6781.5 ft)", lat = "+40 44 22.38", long = "-116 48 09.72"}
KBHX = {id = "BHX", name = "EUREKA (BUNKER HILL)", agency = "NWS", equipment = "RDA", city = "EUREKA", state = "CA", county = "HUMBOLDT", elevation = "732.13 m (2401.57 ft)", lat = "+40 29 54.9", long = "-124 17 31.8"}
PAPD = {id = "APD", name = "FAIRBANKS FAA (RDA 1)", agency = "FAA", equipment = "RDA", city = "FAIRBANKS", state = "AK", county = "N/A", elevation = "790.35 m (2591.86 ft)", lat = "+65 02 06.41", long = "-147 30 05.15"}
KMVX = {id = "MVX", name = "FARGO/GRAND FORKS", agency = "NWS", equipment = "RDA", city = "GRAND FORKS", state = "ND", county = "TRAILL", elevation = "300.53 m (984.25 ft)", lat = "+47 31 40", long = "-97 19 32"}
KFSX = {id = "FSX", name = "FLAGSTAFF (RDA 1)", agency = "NWS", equipment = "RDA", city = "FLAGSTAFF", state = "AZ", county = "COCONINO", elevation = "2260.7 m (7414.7 ft)", lat = "+34 34 27.6", long = "-111 11 54.4"}
KHPX = {id = "HPX", name = "FT CAMPBELL", agency = "AFWA", equipment = "RDA", city = "TRENTON", state = "KY", county = "TODD", elevation = "172 m (564.3 ft)", lat = "+36 44 13.1", long = "-87 17 08.1"}
KTYX = {id = "TYX", name = "FT DRUM", agency = "AFWA", equipment = "RDA", city = "MONTAGUE", state = "NY", county = "LEWIS", elevation = "562.66 m (1843.83 ft)", lat = "+43 45 20.5", long = "-75 40 47.5"}
KGRK = {id = "GRK", name = "FT CAVAZOS", agency = "AFWA", equipment = "RDA", city = "GRANGER", state = "TX", county = "BELL", elevation = "163.98 m (534.78 ft)", lat = "+30 43 18.6", long = "-97 22 58.6"}
KPOE = {id = "POE", name = "FT JOHNSON", agency = "AFWA", equipment = "RDA", city = "FT POLK", state = "LA", county = "VERNON", elevation = "124.36 m (406.82 ft)", lat = "+31 09 19", long = "-92 58 34"}
KEOX = {id = "EOX", name = "FT NOVOSEL", agency = "AFWA", equipment = "RDA", city = "ECHO", state = "AL", county = "DALE", elevation = "144 m (472.44 ft)", lat = "+31 27 38", long = "-85 27 33.8"}
KGGW = {id = "GGW", name = "GLASGOW", agency = "NWS", equipment = "RDA", city = "GLASGOW", state = "MT", county = "VALLEY", elevation = "702 m (2303.15 ft)", lat = "+48 12 22.9", long = "-106 37 28.9"}
KGLD = {id = "GLD", name = "GOODLAND", agency = "NWS", equipment = "RDA", city = "GOODLAND", state = "KS", county = "SHERMAN", elevation = "1112.82 m (3648.29 ft)", lat = "+39 22 01", long = "-101 42 01"}
KUEX = {id = "UEX", name = "GRAND ISLAND", agency = "NWS", equipment = "RDA", city = "BLUE HILL", state = "NE", county = "WEBSTER", elevation = "602.28 m (1975.07 ft)", lat = "+40 19 15", long = "-98 26 31"}
KGJX = {id = "GJX", name = "GRAND JUNCTION (RDA 1)", agency = "NWS", equipment = "RDA", city = "GRAND JUNCTION", state = "CO", county = "MESA", elevation = "3059 m (10036.09 ft)", lat = "+39 03 43.81", long = "-108 12 49.54"}
KGRR = {id = "GRR", name = "GRAND RAPIDS", agency = "NWS", equipment = "RDA", city = "GRAND RAPIDS", state = "MI", county = "KENT", elevation = "237.13 m (777.56 ft)", lat = "+42 53 38", long = "-85 32 41.6"}
KTFX = {id = "TFX", name = "GREAT FALLS", agency = "NWS", equipment = "RDA", city = "GREAT FALLS", state = "MT", county = "CASCADE", elevation = "1140 m (3740.16 ft)", lat = "+47 27 34.5", long = "-111 23 07.2"}
KGRB = {id = "GRB", name = "GREEN BAY", agency = "NWS", equipment = "RDA", city = "GREEN BAY", state = "WI", county = "BROWN", elevation = "216 m (708.66 ft)", lat = "+44 29 55.08", long = "-88 06 40"}
KGSP = {id = "GSP", name = "GREER", agency = "NWS", equipment = "RDA", city = "GREER", state = "SC", county = "SPARTANBURG", elevation = "291 m (954.72 ft)", lat = "+34 52 59.9", long = "-82 13 11.4"}
KHDX = {id = "HDX", name = "HOLLOMAN AFB", agency = "AFWA", equipment = "RDA", city = "RUIDOSO", state = "NM", county = "DONA ANA", elevation = "1286.87 m (4219.16 ft)", lat = "+33 04 37.2", long = "-106 07 12.12"}
KHGX = {id = "HGX", name = "HOUSTON", agency = "NWS", equipment = "RDA", city = "DICKINSON", state = "TX", county = "GALVESTON", elevation = "5.49 m (16.4 ft)", lat = "+29 28 18.84", long = "-95 04 43.44"}
KIND = {id = "IND", name = "INDIANAPOLIS", agency = "NWS", equipment = "RDA", city = "INDIANAPOLIS", state = "IN", county = "MARION", elevation = "240.79 m (787.4 ft)", lat = "+39 42 27", long = "-86 16 49"}
KJKL = {id = "JKL", name = "JACKSON, KY", agency = "NWS", equipment = "RDA", city = "JACKSON", state = "KY", county = "BREATHITT", elevation = "415.75 m (1361.55 ft)", lat = "+37 35 27", long = "-83 18 47"}
KJAX = {id = "JAX", name = "JACKSONVILLE", agency = "NWS", equipment = "RDA", city = "JACKSONVILLE", state = "FL", county = "DUVAL", elevation = "19 m (62.34 ft)", lat = "+30 29 04.68", long = "-81 42 06.84"}
RODN = {id = "ODNR5", name = "KADENA AB", agency = "AFWA", equipment = "RDA", city = "KADENA AB", state = "JA", county = "N/A", elevation = "91 m (298.56 ft)", lat = "+26 18 28.08", long = "+127 54 12.49"}
PHKM = {id = "HKM", name = "KAMUELA/KOHALA APT (RDA 1)", agency = "FAA", equipment = "RDA", city = "KAMUELA", state = "HI", county = "HAWAII", elevation = "1174 m (3851.71 ft)", lat = "+20 07 31", long = "-155 46 40"}
KBYX = {id = "BYX", name = "KEY WEST", agency = "NWS", equipment = "RDA", city = "BOCA CHICA KEY", state = "FL", county = "MONROE", elevation = "2.44 m (6.56 ft)", lat = "+24 35 51", long = "-81 42 11.4"}
PAKC = {id = "AKC", name = "KING SALMON FAA (RDA 1)", agency = "FAA", equipment = "RDA", city = "KING SALMON", state = "AK", county = "N/A", elevation = "19.2 m (62.34 ft)", lat = "+58 40 46", long = "-156 37 46"}
KMRX = {id = "MRX", name = "KNOXVILLE", agency = "NWS", equipment = "RDA", city = "MORRISTOWN", state = "TN", county = "HAMBLEN", elevation = "407.52 m (1335.3 ft)", lat = "+36 10 07", long = "-83 24 07"}
RKJK = {id = "KJKR4", name = "KUNSAN AB", agency = "AFWA", equipment = "RDA", city = "KUNSAN AB", state = "KO", county = "N/A", elevation = "23.77 m (75.46 ft)", lat = "+35 55 27", long = "+126 37 20"}
KARX = {id = "ARX", name = "LA CROSSE", agency = "NWS", equipment = "RDA", city = "LA CROSSE", state = "WI", county = "LA CROSSE", elevation = "388.92 m (1272.97 ft)", lat = "+43 49 22", long = "-91 11 28"}
KLCH = {id = "LCH", name = "LAKE CHARLES", agency = "NWS", equipment = "RDA", city = "LAKE CHARLES", state = "LA", county = "CALCASIEU", elevation = "17 m (55.77 ft)", lat = "+30 07 31.1", long = "-93 12 57.2"}
KESX = {id = "ESX", name = "LAS VEGAS", agency = "NWS", equipment = "RDA", city = "LAS VEGAS", state = "NV", county = "CLARK", elevation = "1483.46 m (4865.49 ft)", lat = "+35 42 04.86", long = "-114 53 29.94"}
KDFX = {id = "DFX", name = "LAUGHLIN AFB", agency = "AFWA", equipment = "RDA", city = "BRACKETVILLE", state = "TX", county = "KINNEY", elevation = "344.73 m (1128.61 ft)", lat = "+29 16 23.3", long = "-100 16 49.2"}
KILX = {id = "ILX", name = "LINCOLN", agency = "NWS", equipment = "RDA", city = "LINCOLN", state = "IL", county = "LOGAN", elevation = "188 m (616.8 ft)", lat = "+40 09 01.8", long = "-89 20 12.45"}
KLZK = {id = "LZK", name = "LITTLE ROCK", agency = "NWS", equipment = "RDA", city = "NORTH LITTLE ROCK", state = "AR", county = "PULASKI", elevation = "173.13 m (567.59 ft)", lat = "+34 50 11.4", long = "-92 15 43.9"}
KVTX = {id = "VTX", name = "LOS ANGELES", agency = "NWS", equipment = "RDA", city = "LOS ANGELES", state = "CA", county = "VENTURA", elevation = "830.88 m (2723.1 ft)", lat = "+34 24 43.26", long = "-119 10 43.5"}
KLVX = {id = "LVX", name = "LOUISVILLE", agency = "NWS", equipment = "RDA", city = "FORT KNOX", state = "KY", county = "HARDIN", elevation = "219.15 m (718.5 ft)", lat = "+37 58 31", long = "-85 56 38"}
KLBB = {id = "LBB", name = "LUBBOCK", agency = "NWS", equipment = "RDA", city = "LUBBOCK", state = "TX", county = "LUBBOCK", elevation = "1005 m (3297.24 ft)", lat = "+33 39 14.9", long = "-101 48 51"}
KMQT = {id = "MQT", name = "MARQUETTE", agency = "NWS", equipment = "RDA", city = "NEGAUNEE", state = "MI", county = "MARQUETTE", elevation = "430.07 m (1410.76 ft)", lat = "+46 31 52", long = "-87 32 54"}
KMXX = {id = "MXX", name = "MAXWELL AFB", agency = "AFWA", equipment = "RDA", city = "CARRVILLE", state = "AL", county = "MACON", elevation = "136 m (446.19 ft)", lat = "+32 32 11.94", long = "-85 47 23.1"}
KMAX = {id = "MAX", name = "MEDFORD (RDA 1)", agency = "NWS", equipment = "RDA", city = "MEDFORD", state = "OR", county = "JACKSON", elevation = "2289.96 m (7509.84 ft)", lat = "+42 04 52.21", long = "-122 43 02.53"}
KMLB = {id = "MLB", name = "MELBOURNE", agency = "NWS", equipment = "RDA", city = "MELBOURNE", state = "FL", county = "BREVARD", elevation = "10.67 m (32.81 ft)", lat = "+28 06 47.5", long = "-80 39 14.7"}
KNQA = {id = "NQA", name = "MEMPHIS", agency = "NWS", equipment = "RDA", city = "MILLINGTON", state = "TN", county = "SHELBY", elevation = "103 m (337.93 ft)", lat = "+35 20 41", long = "-89 52 24"}
KAMX = {id = "AMX", name = "MIAMI", agency = "NWS", equipment = "RDA", city = "MIAMI", state = "FL", county = "DADE", elevation = "4.27 m (13.12 ft)", lat = "+25 36 39.9", long = "-80 24 45.6"}
PAIH = {id = "AIH", name = "MIDDLETON ISLAND (RDA 1)", agency = "FAA", equipment = "RDA", city = "MIDDLETON ISLAND", state = "AK", county = "N/A", elevation = "20.42 m (65.62 ft)", lat = "+59 27 38.76", long = "-146 18 12.41"}
KMAF = {id = "MAF", name = "MIDLAND/ODESSA", agency = "NWS", equipment = "RDA", city = "MIDLAND", state = "TX", county = "MIDLAND", elevation = "883 m (2896.98 ft)", lat = "+31 56 36.46", long = "-102 11 21.3"}
KMKX = {id = "MKX", name = "MILWAUKEE", agency = "NWS", equipment = "RDA", city = "DOUSMAN", state = "WI", county = "WAUKESHA", elevation = "292 m (958.01 ft)", lat = "+42 58 04.44", long = "-88 33 02.4"}
KMPX = {id = "MPX", name = "MINNEAPOLIS", agency = "NWS", equipment = "RDA", city = "CHANHASSEN", state = "MN", county = "CARVER", elevation = "301 m (987.53 ft)", lat = "+44 50 56", long = "-93 33 55.9"}
KMBX = {id = "MBX", name = "MINOT AFB", agency = "AFWA", equipment = "RDA", city = "DEERING", state = "ND", county = "MCHENRY", elevation = "455.07 m (1492.78 ft)", lat = "+48 23 35", long = "-100 51 52"}
KMSX = {id = "MSX", name = "MISSOULA (RDA 1)", agency = "NWS", equipment = "RDA", city = "MISSOULA", state = "MT", county = "MISSOULA", elevation = "2417 m (7929.79 ft)", lat = "+47 02 27.6", long = "-113 59 10.4"}
KMOB = {id = "MOB", name = "MOBILE", agency = "NWS", equipment = "RDA", city = "MOBILE", state = "AL", county = "MOBILE", elevation = "63.4 m (206.69 ft)", lat = "+30 40 46", long = "-88 14 24"}
PHMO = {id = "HMO", name = "MOLOKAI FAA (RDA 1)", agency = "FAA", equipment = "RDA", city = "MOLOKAI", state = "HI", county = "MOLOKAI", elevation = "415.44 m (1361.55 ft)", lat = "+21 07 58", long = "-157 10 49"}
KVAX = {id = "VAX", name = "MOODY AFB", agency = "AFWA", equipment = "RDA", city = "SOUTH STOCKTON", state = "GA", county = "LANIER", elevation = "66 m (216.54 ft)", lat = "+30 53 25", long = "-83 00 06.5"}
KMHX = {id = "MHX", name = "MOREHEAD CITY", agency = "NWS", equipment = "RDA", city = "NEWPORT", state = "NC", county = "CARTERET", elevation = "9.45 m (29.53 ft)", lat = "+34 46 33.27", long = "-76 52 34.28"}
KOHX = {id = "OHX", name = "NASHVILLE", agency = "NWS", equipment = "RDA", city = "OLD HICKORY", state = "TN", county = "WILSON", elevation = "176.48 m (577.43 ft)", lat = "+36 14 50", long = "-86 33 45"}
KAPX = {id = "APX", name = "NCL MICHIGAN", agency = "NWS", equipment = "RDA", city = "GAYLORD", state = "MI", county = "OTSEGO", elevation = "446.23 m (1463.25 ft)", lat = "+44 54 22.86", long = "-84 43 10.32"}
PAEC = {id = "AEC", name = "NOME FAA (RDA 1)", agency = "FAA", equipment = "RDA", city = "NOME", state = "AK", county = "N/A", elevation = "17.68 m (55.77 ft)", lat = "+64 30 41", long = "-165 17 42"}
KAKQ = {id = "AKQ", name = "NORFOLK", agency = "NWS", equipment = "RDA", city = "WAKEFIELD", state = "VA", county = "SUSSEX", elevation = "48 m (157.48 ft)", lat = "+36 59 02.58", long = "-77 00 26.5"}
KTLX = {id = "TLX", name = "NORMAN", agency = "NWS", equipment = "RDA", city = "OKLAHOMA CITY", state = "OK", county = "CLEVELAND", elevation = "369.72 m (1210.63 ft)", lat = "+35 20 00.10", long = "-97 16 39.94"}
KLNX = {id = "LNX", name = "NORTH PLATTE", agency = "NWS", equipment = "RDA", city = "NORTH PLATTE", state = "NE", county = "LOGAN", elevation = "919 m (3015.09 ft)", lat = "+41 57 28.6", long = "-100 34 34.4"}
KHTX = {id = "HTX", name = "NORTHEAST ALABAMA", agency = "NWS", equipment = "RDA", city = "HYTOP", state = "AL", county = "JACKSON", elevation = "537.06 m (1761.81 ft)", lat = "+34 55 50", long = "-86 05 01"}
KIWX = {id = "IWX", name = "NORTHERN INDIANA", agency = "NWS", equipment = "RDA", city = "NORTH WEBSTER", state = "IN", county = "KOSCIUSKO", elevation = "292.3 m (958.01 ft)", lat = "+41 21 31", long = "-85 42 00"}
KOUN = {id = "NORO2", name = "NSSL", agency = "NWS", equipment = "RDA", city = "NORMAN", state = "OK", county = "CLEVELAND", elevation = "370 m (1213.91 ft)", lat = "+35 14 09.81", long = "-97 27 44.46"}
KOAX = {id = "OAX", name = "OMAHA", agency = "NWS", equipment = "RDA", city = "VALLEY", state = "NE", county = "DOUGLAS", elevation = "349.91 m (1145.01 ft)", lat = "+41 19 13.33", long = "-96 22 00.55"}
KPAH = {id = "PAH", name = "PADUCAH", agency = "NWS", equipment = "RDA", city = "PADUCAH", state = "KY", county = "MCCRACKEN", elevation = "119.48 m (390.42 ft)", lat = "+37 04 06", long = "-88 46 19"}
KPDT = {id = "PDT", name = "PENDLETON", agency = "NWS", equipment = "RDA", city = "PENDLETON", state = "OR", county = "UMATILLA", elevation = "461.77 m (1512.47 ft)", lat = "+45 41 26.34", long = "-118 51 10.55"}
KDIX = {id = "DIX", name = "PHILADELPHIA", agency = "NWS", equipment = "RDA", city = "FORT DIX", state = "NJ", county = "BURLINGTON", elevation = "45.42 m (147.64 ft)", lat = "+39 56 49.52", long = "-74 24 38.63"}
KIWA = {id = "IWA", name = "PHOENIX", agency = "NWS", equipment = "RDA", city = "PHOENIX", state = "AZ", county = "MARICOPA", elevation = "415 m (1361.55 ft)", lat = "+33 17 21.24", long = "-111 40 11.7"}
KPBZ = {id = "PBZ", name = "PITTSBURGH", agency = "NWS", equipment = "RDA", city = "CORAOPOLIS", state = "PA", county = "ALLEGHENY", elevation = "361.19 m (1184.38 ft)", lat = "+40 31 54.18", long = "-80 13 04.68"}
KEAX = {id = "EAX", name = "PLEASANT HILL", agency = "NWS", equipment = "RDA", city = "PLEASANT HILL", state = "MO", county = "CASS", elevation = "303.28 m (994.09 ft)", lat = "+38 48 36.9", long = "-94 15 52.1"}
KSFX = {id = "SFX", name = "POCATELLO", agency = "NWS", equipment = "RDA", city = "SPRINGFIELD", state = "ID", county = "BINGHAM", elevation = "1363.68 m (4471.78 ft)", lat = "+43 06 20.16", long = "-112 41 10.08"}
KGYX = {id = "GYX", name = "PORTLAND, ME", agency = "NWS", equipment = "RDA", city = "GRAY", state = "ME", county = "CUMBERLAND", elevation = "124.66 m (406.82 ft)", lat = "+43 53 28.7", long = "-70 15 22.9"}
KRTX = {id = "RTX", name = "PORTLAND, OR", agency = "NWS", equipment = "RDA", city = "PORTLAND", state = "OR", county = "WASHINGTON", elevation = "492 m (1614.17 ft)", lat = "+45 42 54.14", long = "-122 57 54"}
KPUX = {id = "PUX", name = "PUEBLO", agency = "NWS", equipment = "RDA", city = "PUEBLO", state = "CO", county = "PUEBLO", elevation = "1615 m (5298.56 ft)", lat = "+38 27 34.38", long = "-104 10 52.86"}
KDVN = {id = "DVN", name = "QUAD CITIES", agency = "NWS", equipment = "RDA", city = "DAVENPORT", state = "IA", county = "SCOTT", elevation = "229.82 m (751.31 ft)", lat = "+41 36 42", long = "-90 34 51"}
KRAX = {id = "RAX", name = "RALEIGH/DURHAM", agency = "NWS", equipment = "RDA", city = "CLAYTON", state = "NC", county = "WAKE", elevation = "106.07 m (347.77 ft)", lat = "+35 39 55.87", long = "-78 29 23.10"}
KUDX = {id = "UDX", name = "RAPID CITY", agency = "NWS", equipment = "RDA", city = "NEW UNDERWOOD", state = "SD", county = "PENNINGTON", elevation = "939 m (3080.71 ft)", lat = "+44 07 29", long = "-102 49 48"}
KRGX = {id = "RGX", name = "RENO (RDA 1)", agency = "NWS", equipment = "RDA", city = "NIXON", state = "NV", county = "WASHOE", elevation = "2529.54 m (8297.24 ft)", lat = "+39 45 14.6", long = "-119 27 43.3"}
KRIW = {id = "RIW", name = "RIVERTON/LANDER", agency = "NWS", equipment = "RDA", city = "RIVERTON", state = "WY", county = "FREMONT", elevation = "1697.13 m (5567.59 ft)", lat = "+43 03 57.92", long = "-108 28 38.28"}
KFCX = {id = "FCX", name = "ROANOKE", agency = "NWS", equipment = "RDA", city = "ROANOKE", state = "VA", county = "FLOYD", elevation = "874.17 m (2867.45 ft)", lat = "+37 01 27.84", long = "-80 16 26.29"}
KJGX = {id = "JGX", name = "ROBINS AFB", agency = "AFWA", equipment = "RDA", city = "JEFFERSONVILLE", state = "GA", county = "TWIGGS", elevation = "158.8 m (518.37 ft)", lat = "+32 40 32.46", long = "-83 21 03"}
KDAX = {id = "DAX", name = "SACRAMENTO", agency = "NWS", equipment = "RDA", city = "DAVIS", state = "CA", county = "YOLO", elevation = "9.14 m (29.53 ft)", lat = "+38 30 04", long = "-121 40 40.2"}
KMTX = {id = "MTX", name = "SALT LAKE CITY (RDA 1)", agency = "NWS", equipment = "RDA", city = "SALT LAKE CITY", state = "UT", county = "SALT LAKE", elevation = "1975 m (6479.66 ft)", lat = "+41 15 46", long = "-112 26 52"}
KSJT = {id = "SJT", name = "SAN ANGELO", agency = "NWS", equipment = "RDA", city = "SAN ANGELO", state = "TX", county = "TOM GREEN", elevation = "576.07 m (1889.76 ft)", lat = "+31 22 16.6", long = "-100 29 33"}
KNKX = {id = "NKX", name = "SAN DIEGO", agency = "NWS", equipment = "RDA", city = "SAN DIEGO", state = "CA", county = "SAN DIEGO", elevation = "291.08 m (954.72 ft)", lat = "+32 55 08.46", long = "-117 02 30.48"}
KMUX = {id = "MUX", name = "SAN FRANCISCO", agency = "NWS", equipment = "RDA", city = "LOS GATOS", state = "CA", county = "SANTA CLARA", elevation = "1057.35 m (3467.85 ft)", lat = "+37 09 18.8", long = "-121 53 54.4"}
KHNX = {id = "HNX", name = "SAN JOAQUIN VALY", agency = "NWS", equipment = "RDA", city = "HANFORD", state = "CA", county = "KINGS", elevation = "74.07 m (242.78 ft)", lat = "+36 18 51.05", long = "-119 37 55.7"}
TJUA = {id = "JUA", name = "SAN JUAN FAA (RDA 1)", agency = "FAA", equipment = "RDA", city = "SAN JUAN", state = "PR", county = "N/A", elevation = "867 m (2844.49 ft)", lat = "+18 06 56.4", long = "-66 04 41.4"}
KSOX = {id = "SOX", name = "SANTA ANA MTS", agency = "NWS", equipment = "RDA", city = "SANTA ANA MOUNTAINS", state = "CA", county = "ORANGE", elevation = "927 m (3041.34 ft)", lat = "+33 49 03.84", long = "-117 38 09.6"}
KATX = {id = "ATX", name = "SEATTLE", agency = "NWS", equipment = "RDA", city = "EVERETT", state = "WA", county = "ISLAND", elevation = "161 m (528.22 ft)", lat = "+48 11 40.6", long = "-122 29 44.5"}
KSHV = {id = "SHV", name = "SHREVEPORT", agency = "NWS", equipment = "RDA", city = "SHREVEPORT", state = "LA", county = "CADDO", elevation = "83.21 m (272.31 ft)", lat = "+32 27 03", long = "-93 50 28.5"}
KFSD = {id = "FSD", name = "SIOUX FALLS", agency = "NWS", equipment = "RDA", city = "SIOUX FALLS", state = "SD", county = "MINNEHAHA", elevation = "435.86 m (1427.17 ft)", lat = "+43 35 16", long = "-96 43 46"}
PACG = {id = "ACG", name = "SITKA FAA (RDA 1)", agency = "FAA", equipment = "RDA", city = "BIORKA ISLAND", state = "AK", county = "N/A", elevation = "63.09 m (206.69 ft)", lat = "+56 51 10", long = "-135 31 45"}
PHKI = {id = "HKI", name = "SOUTH KAUAI FAA (RDA 1)", agency = "FAA", equipment = "RDA", city = "SOUTH KAUAI", state = "HI", county = "KAUAI", elevation = "69 m (226.38 ft)", lat = "+21 53 38", long = "-159 33 09"}
PHWA = {id = "HWA", name = "SOUTH SHORE FAA (RDA 1)", agency = "FAA", equipment = "RDA", city = "NAALEHU", state = "HI", county = "HAWAII", elevation = "420.62 m (1377.95 ft)", lat = "+19 05 42", long = "-155 34 08"}
KOTX = {id = "OTX", name = "SPOKANE", agency = "NWS", equipment = "RDA", city = "SPOKANE", state = "WA", county = "SPOKANE", elevation = "726.64 m (2381.89 ft)", lat = "+47 40 49.5", long = "-117 37 36.4"}
KSGF = {id = "SGF", name = "SPRINGFIELD", agency = "NWS", equipment = "RDA", city = "SPRINGFIELD", state = "MO", county = "GREENE", elevation = "389.53 m (1276.25 ft)", lat = "+37 14 06.86", long = "-93 24 01.51"}
KLSX = {id = "LSX", name = "ST LOUIS", agency = "NWS", equipment = "RDA", city = "WELDON SPRING", state = "MO", county = "ST CHARLES", elevation = "185.32 m (606.96 ft)", lat = "+38 41 55", long = "-90 40 58"}
KCCX = {id = "CCX", name = "STATE COLLEGE", agency = "NWS", equipment = "RDA", city = "STATE COLLEGE", state = "PA", county = "CENTRE", elevation = "733.04 m (2404.86 ft)", lat = "+40 55 23.4", long = "-78 00 13.4"}
KLWX = {id = "LWX", name = "STERLING", agency = "NWS", equipment = "RDA", city = "STERLING", state = "VA", county = "LOUDOUN", elevation = "88.54 m (288.71 ft)", lat = "+38 58 34", long = "-77 29 15"}
KTLH = {id = "TLH", name = "TALLAHASSEE", agency = "NWS", equipment = "RDA", city = "TALLAHASSEE", state = "FL", county = "LEON", elevation = "19.2 m (62.34 ft)", lat = "+30 23 51.3", long = "-84 19 44.2"}
KTBW = {id = "TBW", name = "TAMPA", agency = "NWS", equipment = "RDA", city = "RUSKIN", state = "FL", county = "HILLSBOROUGH", elevation = "12.5 m (39.37 ft)", lat = "+27 42 19.8", long = "-82 24 06.4"}
KTWX = {id = "TWX", name = "TOPEKA", agency = "NWS", equipment = "RDA", city = "TOPEKA", state = "KS", county = "WABAUNSEE", elevation = "416.66 m (1364.83 ft)", lat = "+38 59 49.02", long = "-96 13 57.18"}
KEMX = {id = "EMX", name = "TUCSON", agency = "NWS", equipment = "RDA", city = "TUCSON", state = "AZ", county = "PIMA", elevation = "1586.48 m (5203.41 ft)", lat = "+31 53 37.14", long = "-110 37 48.9"}
KINX = {id = "INX", name = "TULSA", agency = "NWS", equipment = "RDA", city = "INOLA", state = "OK", county = "ROGERS", elevation = "203.61 m (666.01 ft)", lat = "+36 10 30.47", long = "-95 33 50.98"}
KVNX = {id = "VNX", name = "VANCE AFB", agency = "AFWA", equipment = "RDA", city = "CHEROKEE", state = "OK", county = "ALFALFA", elevation = "368.81 m (1207.35 ft)", lat = "+36 44 26.22", long = "-98 07 39.78"}
KVBX = {id = "VBX", name = "VANDENBERG SFB", agency = "AFWA", equipment = "RDA", city = "ORCUTT", state = "CA", county = "SANTA BARBARA", elevation = "383 m (1256.56 ft)", lat = "+34 50 18.78", long = "-120 23 52.50"}
KSRX = {id = "SRX", name = "WESTERN ARKANSAS", agency = "NWS", equipment = "RDA", city = "CHAFFEE RIDGE", state = "AR", county = "SEBASTIAN", elevation = "200 m (656.17 ft)", lat = "+35 17 25.5", long = "-94 21 42.8"}
KICT = {id = "ICT", name = "WICHITA", agency = "NWS", equipment = "RDA", city = "WICHITA", state = "KS", county = "SEDGWICK", elevation = "406.91 m (1332.02 ft)", lat = "+37 39 16", long = "-97 26 35"}
KLTX = {id = "LTX", name = "WILMINGTON", agency = "NWS", equipment = "RDA", city = "SHALLOTTE", state = "NC", county = "BRUNSWICK", elevation = "19.51 m (62.34 ft)", lat = "+33 59 20.94", long = "-78 25 44.79"}
KYUX = {id = "YUX", name = "YUMA (RDA 1)", agency = "NWS", equipment = "RDA", city = "YUMA", state = "AZ", county = "PIMA", elevation = "53.04 m (173.88 ft)", lat = "+32 29 43.01", long = "-114 39 24.16"}
KVWX = {id = "VWX", name = "EVANSVILLE, IN", agency = "NWS", equipment = "RDA", city = "OWENSVILLE", state = "IN", county = "GIBSON", elevation = "155.75 m (508.53 ft)", lat = "+38 15 36.9", long = "-87 43 28.3"}
KDGX = {id = "DGX", name = "JACKSON/BRANDON, MS", agency = "NWS", equipment = "RDA", city = "BRANDON", state = "MS", county = "RANKIN", elevation = "150.92 m (492.13 ft)", lat = "+32 16 47.8", long = "-89 59 04"}
KLGX = {id = "LGX", name = "LANGLEY HILL (NW WASHINGTON)", agency = "NWS", equipment = "RDA", city = "LANGLEY HILL", state = "WA", county = "GRAYS HARBOR", elevation = "76.8 m (249.34 ft)", lat = "+47 07 01", long = "-124 06 24"}
KHDC = {id = "HDC", name = "HAMMOND", agency = "NWS", equipment = "RDA", city = "HAMMOND", state = "LA", county = "TANGIPAHOA", elevation = "13 m (42.65 ft)", lat = "+30 31 9.5", long = "-90 24 26.5"}

View file

@ -7,4 +7,5 @@ edition = "2021"
thiserror = "2"
tracing = "0.1"
png = "0.17"
image = "0.25"
image = "0.25"
wxbox-nommer = { version = "0.1", path = "../nommer" }

View file

@ -1,9 +1,8 @@
pub mod error;
mod nommer;
pub mod wgs84;
use crate::error::GribError;
use crate::nommer::NomReader;
use wxbox_nommer::NomReader;
use crate::wgs84::LatLong;
use crate::LatLongVectorRelativity::{EasterlyAndNortherly, IncreasingXY};
use image::{DynamicImage, ImageFormat, ImageReader};

6
crates/nommer/Cargo.toml Normal file
View file

@ -0,0 +1,6 @@
[package]
name = "wxbox-nommer"
version = "0.1.0"
edition = "2024"
[dependencies]

View file

@ -24,6 +24,11 @@ impl<R: Read> NomReader<R> {
self.inner.read_exact(&mut buf)?;
Ok(u32::from_be_bytes(buf))
}
pub fn read_i32(&mut self) -> Result<i32, std::io::Error> {
let mut buf = [0u8; 4];
self.inner.read_exact(&mut buf)?;
Ok(i32::from_be_bytes(buf))
}
pub fn read_u64(&mut self) -> Result<u64, std::io::Error> {
let mut buf = [0u8; 8];
self.inner.read_exact(&mut buf)?;

View file

@ -20,6 +20,7 @@ anyhow = "1"
# data parsing
wxbox-grib2 = { version = "0.1", path = "../grib2" }
wxbox-ar2 = { version = "0.1", path = "../ar2" }
# configuration
serde = { version = "1", features = ["derive"] }

View file

@ -14,3 +14,16 @@ Color: 80 128 128 128
"""
missing = -99.0
no_coverage = -999.0
[data.nexrad.kcrp_ref_test]
from = "aaaa"
palette = """
Color: 10 164 164 255 100 100 192
Color: 20 64 128 255 32 64 128
Color: 30 0 255 0 0 128 0
Color: 40 255 255 0 255 128 0
Color: 50 255 0 0 160 0 0
Color: 60 255 0 255 128 0 128
Color: 70 255 255 255 128 128 128
Color: 80 128 128 128
"""

View file

@ -1,5 +1,6 @@
use crate::grib2::Grib2DataConfig;
use serde::{Deserialize, Serialize};
use crate::nexrad::NexradDataConfig;
#[derive(Serialize, Deserialize)]
pub struct Config {
@ -9,4 +10,5 @@ pub struct Config {
#[derive(Serialize, Deserialize)]
pub struct DataSources {
pub grib2: Grib2DataConfig,
pub nexrad: NexradDataConfig,
}

View file

@ -2,6 +2,7 @@ mod config;
mod error;
mod grib2;
mod tiles;
mod nexrad;
use crate::config::Config;
use crate::grib2::{Grib2DataCache, Grib2TileCache, grib2_handler};
@ -15,11 +16,16 @@ use std::sync::Arc;
use tracing_subscriber::fmt::format::FmtSpan;
use tracing_subscriber::util::SubscriberInitExt;
use wxbox_grib2::GribMessage;
use crate::nexrad::{nexrad_handler, NexradDataCache, NexradTileCache};
#[derive(Clone)]
pub struct AppState {
grib2_data_cache: Grib2DataCache,
grib2_tile_cache: Grib2TileCache,
nexrad_data_cache: NexradDataCache,
nexrad_tile_cache: NexradTileCache,
config: Arc<Config>,
}
@ -41,15 +47,20 @@ async fn main() -> anyhow::Result<()> {
let grib2_data_cache: Grib2DataCache = Cache::new(10_000);
let grib2_tile_cache: Grib2TileCache = Cache::new(10_000);
let nexrad_data_cache: NexradDataCache = Cache::new(10_000);
let nexrad_tile_cache: NexradTileCache = Cache::new(10_000);
let state = AppState {
grib2_tile_cache,
grib2_data_cache,
nexrad_tile_cache,
nexrad_data_cache,
config: Arc::new(config),
};
let app = Router::new()
.route("/grib2/{source}/{z}/{x}/{y}", get(grib2_handler))
.route("/nexrad/{source}/{z}/{x}/{y}", get(nexrad_handler))
.with_state(state);
let listener = tokio::net::TcpListener::bind("[::]:3000").await?;

234
crates/tiler/src/nexrad.rs Normal file
View file

@ -0,0 +1,234 @@
use crate::AppState;
use crate::error::AppError;
use crate::tiles::{DataId, TileId};
use anyhow::{anyhow, bail};
use axum::extract::{Path, State};
use axum::http::{StatusCode, header};
use axum::response::IntoResponse;
use flate2::read::GzDecoder;
use image::codecs::png::PngEncoder;
use image::{Rgba, RgbaImage};
use moka::future::Cache;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::f64::consts::PI;
use std::fmt::Debug;
use std::io;
use std::io::{Cursor, ErrorKind};
use std::num::TryFromIntError;
use std::sync::Arc;
use tokio::io::AsyncReadExt;
use tracing::{debug, info_span};
use wxbox_ar2::{parse, MomentValue, Scan, DATA_BYTES};
use wxbox_ar2::sites::wsr88d::SITES;
use wxbox_grib2::GribMessage;
use wxbox_grib2::wgs84::LatLong;
use wxbox_pal::{ColorPalette, Palette};
pub type NexradDataCache = Cache<DataId, Arc<wxbox_ar2::Scan>>;
pub type NexradTileCache = Cache<TileId, Arc<Vec<u8>>>;
pub type NexradDataConfig = HashMap<String, NexradDataSource>;
#[derive(Serialize, Deserialize, Clone)]
pub struct NexradDataSource {
pub from: String,
pub palette: String,
}
impl Debug for NexradDataSource {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "<nexrad data source>")
}
}
#[tracing::instrument(level = "info")]
pub async fn nexrad_handler(
Path((source, z, x, y)): Path<(String, usize, usize, String)>,
State(state): State<AppState>,
) -> Result<impl IntoResponse, AppError> {
let mut y = y
.strip_suffix(".png")
.ok_or(io::Error::new(ErrorKind::InvalidInput, "invalid"))?;
let mut size = 256;
if y.ends_with("@2x") {
size = 512;
y = y.strip_suffix("@2x").unwrap();
}
let y: usize = y.parse()?;
let tile_id = TileId {
source,
z,
x,
y,
size,
};
// do we have a pre-prepared tile? if so, return it immediately
if let Some(tile) = state.nexrad_tile_cache.get(&tile_id).await {
return Ok(([(header::CONTENT_TYPE, "image/png")], tile.as_ref().clone()));
}
// is this even a valid data source?
let data_id = tile_id.data_id();
let Some(ds) = state.config.data.nexrad.get(&data_id.source) else {
return Err(anyhow!("invalid/unknown nexrad state").into());
};
// ok, so we don't have a tile image yet
// this means we are going to have to kick off a task to put that in the cache
// lets check if we have the raw data
let data = if !state.nexrad_data_cache.contains_key(&data_id) {
// we don't, so let's start by starting a task for that
load_nexrad_data(state.nexrad_data_cache, data_id, ds.clone()).await?
} else {
state.nexrad_data_cache.get(&data_id).await.unwrap()
};
// we know we need to build the tile, so let's do that now
// it also returns it, so we can conveniently return it right now
let pixel_data =
render_to_png(state.nexrad_tile_cache.clone(), data, tile_id, ds.clone()).await?;
Ok((
[(header::CONTENT_TYPE, "image/png")],
pixel_data.as_ref().clone(),
))
}
async fn load_nexrad_data(
cache: NexradDataCache,
data_id: DataId,
data_source: NexradDataSource,
) -> anyhow::Result<Arc<Scan>> {
/*
let client = reqwest::Client::new();
let r = client.get(data_source.from.as_str()).send().await?;
if !r.status().is_success() {
bail!("nexrad data failed to load: {}", r.status());
}*/
let bytes = DATA_BYTES.to_vec();
let data = Arc::new(parse(bytes)?);
cache.insert(data_id, data.clone()).await;
Ok(data)
}
const TWO_PI: f64 = PI * 2.0;
const HALF_PI: f64 = PI / 2.0;
const A: f64 = 6_378.1370 * 1000.0; // m
const B: f64 = 6_356.7523 * 1000.0; // m
async fn render_to_png(
cache: NexradTileCache,
data: Arc<Scan>,
tile_id: TileId,
data_source: NexradDataSource,
) -> anyhow::Result<Arc<Vec<u8>>> {
let span = info_span!("render_to_png");
let span = span.enter();
let mut image = RgbaImage::new(tile_id.size as u32, tile_id.size as u32);
let n = 2_usize.pow(tile_id.z as u32) as f64 * tile_id.size as f64;
let tile_x_times_tilesize = tile_id.x as f64 * tile_id.size as f64;
let tile_y_times_tilesize = tile_id.y as f64 * tile_id.size as f64;
for x in 0..tile_id.size {
for y in 0..tile_id.size {
let x_cartesian = (tile_x_times_tilesize + x as f64) / n;
let y_cartesian = (tile_y_times_tilesize + y as f64) / n;
let radar = SITES.sites.get("KCRP").unwrap();
let radar_theta = radar.long;
let radar_phi = radar.lat;
let radar_r = ((A.powi(2) * radar_phi.cos()).powi(2) + (B.powi(2) * radar_phi.sin()).powi(2)
/ (A * radar_phi.cos()).powi(2) + (B * radar_phi.sin()).powi(2)).sqrt();
let radar_x = radar_r * radar_theta.cos() * radar_phi.sin();
let radar_y = radar_r * radar_theta.sin() * radar_phi.sin();
let radar_z = radar_r * radar_theta.cos();
let measurement_theta = (TWO_PI * x_cartesian - PI).to_degrees();
let measurement_phi = ((PI - TWO_PI * y_cartesian).exp().atan() * 2.0_f64 - HALF_PI).to_degrees();
let measurement_r = ((A.powi(2) * measurement_phi.cos()).powi(2) + (B.powi(2) * measurement_phi.sin()).powi(2)
/ (A * measurement_phi.cos()).powi(2) + (B * measurement_phi.sin()).powi(2)).sqrt();
let measurement_x = measurement_r * measurement_theta.cos() * measurement_phi.sin();
let measurement_y = measurement_r * measurement_theta.sin() * measurement_phi.sin();
let measurement_z = measurement_r * measurement_theta.cos();
let radar_local_x = measurement_x - radar_x;
let radar_local_y = measurement_y - radar_y;
let radar_local_z = measurement_z - radar_z;
let radar_local_r = (radar_local_x.powi(2) + radar_local_y.powi(2) + radar_local_z.powi(2)).sqrt();
let radar_local_theta = (radar_local_y / radar_local_x).atan();
let radar_local_phi = (radar_local_z / radar_local_r).acos();
let azimuth = radar_local_theta;
let _elevation = radar_local_phi;
let distance = radar_local_r;
let data = data.sweeps.get(0).unwrap();
let first_radial = data.radials.get(0).unwrap();
let azimuth_spacing = first_radial.azimuth_spacing_degrees;
let azimuth_number = (azimuth / azimuth_spacing as f64).floor() as usize;
let radial = data.radials.get(azimuth_number).unwrap();
let data = radial.reflectivity.as_ref().unwrap();
let distance_minus_offset = distance - data.start_range as f64;
let distance_gate = (distance_minus_offset / data.sample_interval as f64).floor() as usize;
let values = data.values();
println!("{}", distance_gate);
let value = values.get(distance_gate).unwrap();
let color = colorize(Some(value), &data_source)?;
image.put_pixel(x as u32, y as u32, color);
}
}
// encode to png bytes and return
let mut result = vec![];
let encoder = PngEncoder::new(&mut result);
image.write_with_encoder(encoder)?;
let output = Arc::new(result);
drop(span);
cache.insert(tile_id, output.clone()).await;
Ok(output)
}
fn colorize(value: Option<&MomentValue>, data_source: &NexradDataSource) -> anyhow::Result<Rgba<u8>> {
Ok(match value {
Some(MomentValue::BelowThreshold) => Rgba([0, 0, 0, 0]),
Some(MomentValue::RangeFolded) => Rgba([119, 0, 125, 255]),
Some(MomentValue::Value(value)) => {
let color = wxbox_pal::parser::parse(&data_source.palette)?.colorize(*value as f64);
Rgba([
color.red,
color.green,
color.blue,
if color.red == 0 && color.green == 0 && color.blue == 0 {
0
} else {
255
},
])
}
None => Rgba([0, 0, 0, 30]),
})
}