diff --git a/.idea/wxbox.iml b/.idea/wxbox.iml
index cab36b6..66a32a2 100644
--- a/.idea/wxbox.iml
+++ b/.idea/wxbox.iml
@@ -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" />
diff --git a/Cargo.lock b/Cargo.lock
index ad9d6d2..ca75648 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -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"
diff --git a/crates/ar2/Cargo.toml b/crates/ar2/Cargo.toml
new file mode 100644
index 0000000..051d649
--- /dev/null
+++ b/crates/ar2/Cargo.toml
@@ -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"
\ No newline at end of file
diff --git a/crates/ar2/KCRP20170825_235733_V06 b/crates/ar2/KCRP20170825_235733_V06
new file mode 100644
index 0000000..b2e0964
Binary files /dev/null and b/crates/ar2/KCRP20170825_235733_V06 differ
diff --git a/crates/ar2/src/lib.rs b/crates/ar2/src/lib.rs
new file mode 100644
index 0000000..4f9d8b5
--- /dev/null
+++ b/crates/ar2/src/lib.rs
@@ -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");
\ No newline at end of file
diff --git a/crates/ar2/src/main.rs b/crates/ar2/src/main.rs
new file mode 100644
index 0000000..cee21d2
--- /dev/null
+++ b/crates/ar2/src/main.rs
@@ -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());
+}
\ No newline at end of file
diff --git a/crates/ar2/src/sites/mod.rs b/crates/ar2/src/sites/mod.rs
new file mode 100644
index 0000000..91f9f0c
--- /dev/null
+++ b/crates/ar2/src/sites/mod.rs
@@ -0,0 +1 @@
+pub mod wsr88d;
diff --git a/crates/ar2/src/sites/wsr88d.rs b/crates/ar2/src/sites/wsr88d.rs
new file mode 100644
index 0000000..efc373e
--- /dev/null
+++ b/crates/ar2/src/sites/wsr88d.rs
@@ -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;
+    }
+}
\ No newline at end of file
diff --git a/crates/ar2/src/sites/wsr88d.toml b/crates/ar2/src/sites/wsr88d.toml
new file mode 100644
index 0000000..9ec2c3c
--- /dev/null
+++ b/crates/ar2/src/sites/wsr88d.toml
@@ -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"}
diff --git a/crates/grib2/Cargo.toml b/crates/grib2/Cargo.toml
index 440cbee..02b8813 100644
--- a/crates/grib2/Cargo.toml
+++ b/crates/grib2/Cargo.toml
@@ -7,4 +7,5 @@ edition = "2021"
 thiserror = "2"
 tracing = "0.1"
 png = "0.17"
-image = "0.25"
\ No newline at end of file
+image = "0.25"
+wxbox-nommer = { version = "0.1", path = "../nommer" }
\ No newline at end of file
diff --git a/crates/grib2/src/lib.rs b/crates/grib2/src/lib.rs
index ba1862e..03eb77f 100644
--- a/crates/grib2/src/lib.rs
+++ b/crates/grib2/src/lib.rs
@@ -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};
diff --git a/crates/nommer/Cargo.toml b/crates/nommer/Cargo.toml
new file mode 100644
index 0000000..96d9607
--- /dev/null
+++ b/crates/nommer/Cargo.toml
@@ -0,0 +1,6 @@
+[package]
+name = "wxbox-nommer"
+version = "0.1.0"
+edition = "2024"
+
+[dependencies]
diff --git a/crates/grib2/src/nommer.rs b/crates/nommer/src/lib.rs
similarity index 87%
rename from crates/grib2/src/nommer.rs
rename to crates/nommer/src/lib.rs
index 7f8059b..e4c9dc5 100644
--- a/crates/grib2/src/nommer.rs
+++ b/crates/nommer/src/lib.rs
@@ -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)?;
diff --git a/crates/tiler/Cargo.toml b/crates/tiler/Cargo.toml
index b6c8191..8ef7458 100644
--- a/crates/tiler/Cargo.toml
+++ b/crates/tiler/Cargo.toml
@@ -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"] }
diff --git a/crates/tiler/config.toml b/crates/tiler/config.toml
index 18c9005..e50a3ca 100644
--- a/crates/tiler/config.toml
+++ b/crates/tiler/config.toml
@@ -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
+"""
\ No newline at end of file
diff --git a/crates/tiler/src/config.rs b/crates/tiler/src/config.rs
index ea226eb..cfb16d4 100644
--- a/crates/tiler/src/config.rs
+++ b/crates/tiler/src/config.rs
@@ -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,
 }
diff --git a/crates/tiler/src/main.rs b/crates/tiler/src/main.rs
index d27ad3b..2eec30c 100644
--- a/crates/tiler/src/main.rs
+++ b/crates/tiler/src/main.rs
@@ -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?;
diff --git a/crates/tiler/src/nexrad.rs b/crates/tiler/src/nexrad.rs
new file mode 100644
index 0000000..14954c6
--- /dev/null
+++ b/crates/tiler/src/nexrad.rs
@@ -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]),
+    })
+}