chore: praise be the clippy gods
Some checks are pending
Verify Latest Dependencies / Verify Latest Dependencies (push) Waiting to run
build and test / wxbox - latest (push) Waiting to run

This commit is contained in:
core 2025-05-19 10:38:23 -04:00
parent fe4a47d6cc
commit e4dcb48878
9 changed files with 55 additions and 90 deletions

View file

@ -1,6 +1,3 @@
use std::{env, fs};
use std::hint::black_box;
use std::time::Instant;
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion};
use wxbox_ar2::parse; use wxbox_ar2::parse;

View file

@ -1,3 +1,5 @@
#![warn(clippy::all)]
use nexrad_data::volume::File; use nexrad_data::volume::File;
use nexrad_decode::messages::MessageContents; use nexrad_decode::messages::MessageContents;
use nexrad_decode::messages::digital_radar_data::{GenericDataBlock, RadialStatus}; use nexrad_decode::messages::digital_radar_data::{GenericDataBlock, RadialStatus};
@ -46,7 +48,6 @@ pub struct MomentData {
impl MomentData { impl MomentData {
/// Values from this data moment corresponding to gates in the radial. /// Values from this data moment corresponding to gates in the radial.
pub fn values(&self) -> Vec<MomentValue> { pub fn values(&self) -> Vec<MomentValue> {
let copied_values = self.values.iter().copied(); let copied_values = self.values.iter().copied();
@ -67,9 +68,7 @@ impl MomentData {
} }
/// The data moment value for a product in a radial's gate. The value may be a floating-point number /// 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". /// or a special case such as "below threshold" or "range folded".
#[derive(Debug, Clone, Copy, PartialEq)] #[derive(Debug, Clone, Copy, PartialEq)]
pub enum MomentValue { pub enum MomentValue {
@ -89,7 +88,7 @@ pub fn parse(input: Vec<u8>) -> nexrad_data::result::Result<Scan> {
let mut vcp: Option<u16> = None; let mut vcp: Option<u16> = None;
let mut radials = vec![]; let mut radials = vec![];
let mut radial_chunks: Vec<nexrad_data::result::Result<(Vec<Radial>, Option<u16>)>> = file.records() let radial_chunks: Vec<nexrad_data::result::Result<(Vec<Radial>, Option<u16>)>> = file.records()
.par_iter_mut() .par_iter_mut()
.map(|record| { .map(|record| {
let mut vcp = None; let mut vcp = None;
@ -145,23 +144,23 @@ fn into_radial(
radial_status: message.header.radial_status(), radial_status: message.header.radial_status(),
elevation_number: message.header.elevation_number, elevation_number: message.header.elevation_number,
elevation_number_degrees: message.header.elevation_angle, elevation_number_degrees: message.header.elevation_angle,
reflectivity: message.reflectivity_data_block.map(|u| into_moment_data(u)), reflectivity: message.reflectivity_data_block.map(into_moment_data),
velocity: message.velocity_data_block.map(|u| into_moment_data(u)), velocity: message.velocity_data_block.map(into_moment_data),
spectrum_width: message spectrum_width: message
.spectrum_width_data_block .spectrum_width_data_block
.map(|u| into_moment_data(u)), .map(into_moment_data),
differential_reflectivity: message differential_reflectivity: message
.differential_reflectivity_data_block .differential_reflectivity_data_block
.map(|u| into_moment_data(u)), .map(into_moment_data),
differential_phase: message differential_phase: message
.differential_phase_data_block .differential_phase_data_block
.map(|u| into_moment_data(u)), .map(into_moment_data),
correlation_coefficient: message correlation_coefficient: message
.correlation_coefficient_data_block .correlation_coefficient_data_block
.map(|u| into_moment_data(u)), .map(into_moment_data),
specific_differential_phase: message specific_differential_phase: message
.specific_diff_phase_data_block .specific_diff_phase_data_block
.map(|u| into_moment_data(u)), .map(into_moment_data),
}) })
} }

View file

@ -1,5 +1,4 @@
use std::{env, fs}; use std::{env, fs};
use std::fs::File;
use std::time::Instant; use std::time::Instant;
use wxbox_ar2::parse; use wxbox_ar2::parse;

View file

@ -1,6 +1,6 @@
use serde::{Deserialize, Deserializer}; use serde::{Deserialize, Deserializer};
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::{LazyLock, OnceLock}; use std::sync::LazyLock;
#[derive(Deserialize, Debug)] #[derive(Deserialize, Debug)]
pub struct Wsr88dSite { pub struct Wsr88dSite {
@ -30,7 +30,7 @@ where
{ {
let s: String = Deserialize::deserialize(deserializer)?; let s: String = Deserialize::deserialize(deserializer)?;
let elevation = s.split(" ").nth(0).unwrap(); let elevation = s.split(" ").next().unwrap();
Ok(elevation.parse().unwrap()) Ok(elevation.parse().unwrap())
} }

View file

@ -1,7 +1,4 @@
use std::{env, fs};
use std::hint::black_box;
use std::time::Instant;
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion};
use wxbox_grib2::GribMessage; use wxbox_grib2::GribMessage;

View file

@ -2,28 +2,24 @@ use crate::AppState;
use crate::error::AppError; use crate::error::AppError;
use crate::tiles::{DataId, TileId}; use crate::tiles::{DataId, TileId};
use anyhow::{anyhow, bail}; use anyhow::{anyhow, bail};
use axum::Json;
use axum::extract::{Path, State}; use axum::extract::{Path, State};
use axum::http::{StatusCode, header}; use axum::http::header;
use axum::response::IntoResponse; use axum::response::IntoResponse;
use flate2::read::GzDecoder; use flate2::read::GzDecoder;
use image::codecs::png::PngEncoder; use image::codecs::png::PngEncoder;
use image::{Rgba, RgbaImage}; use image::{Rgba, RgbaImage};
use moka::future::Cache; use moka::future::Cache;
use rayon::iter::IntoParallelIterator; use rayon::iter::IntoParallelIterator;
use serde::{Deserialize, Serialize};
use std::collections::HashMap; use std::collections::HashMap;
use std::f64::consts::PI; use std::f64::consts::PI;
use std::fmt::Debug;
use std::io; use std::io;
use std::io::{Cursor, ErrorKind}; use std::io::{Cursor, ErrorKind};
use std::num::TryFromIntError;
use std::sync::Arc; use std::sync::Arc;
use axum::Json; use tracing::info_span;
use tokio::io::AsyncReadExt;
use tracing::{debug, info_span};
use wxbox_grib2::GribMessage; use wxbox_grib2::GribMessage;
use wxbox_grib2::wgs84::LatLong; use wxbox_grib2::wgs84::LatLong;
use wxbox_pal::{ColorPalette, Palette}; use wxbox_pal::ColorPalette;
use rayon::iter::ParallelIterator; use rayon::iter::ParallelIterator;
use wxbox_common::{Grib2DataSource, GribMessageTimeMetadata, GribTileMetadata}; use wxbox_common::{Grib2DataSource, GribMessageTimeMetadata, GribTileMetadata};
@ -35,13 +31,11 @@ pub type Grib2DataConfig = HashMap<String, Grib2DataSource>;
#[tracing::instrument(level = "info")] #[tracing::instrument(level = "info")]
pub async fn grib2_metadata( pub async fn grib2_metadata(
Path((source)): Path<(String)>, Path(source): Path<String>,
State(state): State<AppState>, State(state): State<AppState>,
) -> Result<Json<wxbox_common::GribTileMetadata>, AppError> { ) -> Result<Json<wxbox_common::GribTileMetadata>, AppError> {
// is this even a valid data source? // is this even a valid data source?
let data_id = DataId { let data_id = DataId { source };
source,
};
let Some(ds) = state.config.data.grib2.get(&data_id.source) else { let Some(ds) = state.config.data.grib2.get(&data_id.source) else {
return Err(anyhow!("invalid/unknown grib2 state").into()); return Err(anyhow!("invalid/unknown grib2 state").into());
}; };
@ -185,10 +179,7 @@ async fn render_to_png(
let nearest_value = data.value_for(LatLong { lat, long }).map(|u| u as f64); let nearest_value = data.value_for(LatLong { lat, long }).map(|u| u as f64);
let color = colorize(nearest_value, &data_source).unwrap_or(Rgba::from([0, 0, 0, 0]))
colorize(nearest_value, &data_source).unwrap_or(Rgba::from([0, 0, 0, 0]));
color
}) })
.collect::<Vec<_>>() .collect::<Vec<_>>()
}) })

View file

@ -2,25 +2,22 @@ mod config;
mod error; mod error;
mod grib2; mod grib2;
mod nexrad; mod nexrad;
mod tiles;
mod nexrad_list_response; mod nexrad_list_response;
mod tiles;
use crate::config::Config; use crate::config::Config;
use crate::grib2::{Grib2DataCache, Grib2TileCache, grib2_handler, grib2_metadata}; use crate::grib2::{Grib2DataCache, Grib2TileCache, grib2_handler, grib2_metadata};
use crate::nexrad::{NexradDataCache, NexradTileCache, nexrad_handler}; use crate::nexrad::{NexradDataCache, NexradTileCache, nexrad_handler};
use crate::tiles::{DataId, TileId};
use axum::Router; use axum::Router;
use axum::http::Method;
use axum::routing::get; use axum::routing::get;
use moka::future::Cache; use moka::future::Cache;
use std::env::args; use std::env::args;
use std::fmt::{Debug, Formatter}; use std::fmt::{Debug, Formatter};
use std::sync::Arc; use std::sync::Arc;
use axum::http::Method;
use tower_http::cors::{Any, CorsLayer}; use tower_http::cors::{Any, CorsLayer};
use tracing_subscriber::EnvFilter; use tracing_subscriber::EnvFilter;
use tracing_subscriber::fmt::format::FmtSpan; use tracing_subscriber::fmt::format::FmtSpan;
use tracing_subscriber::util::SubscriberInitExt;
use wxbox_grib2::GribMessage;
#[derive(Clone)] #[derive(Clone)]
pub struct AppState { pub struct AppState {

View file

@ -1,11 +1,12 @@
use crate::AppState; use crate::AppState;
use crate::error::AppError; use crate::error::AppError;
use crate::nexrad_list_response::ListBucketResult;
use crate::tiles::{DataId, TileId}; use crate::tiles::{DataId, TileId};
use anyhow::{anyhow, bail}; use anyhow::{anyhow, bail};
use axum::extract::{Path, State}; use axum::extract::{Path, State};
use axum::http::{StatusCode, header}; use axum::http::header;
use axum::response::IntoResponse; use axum::response::IntoResponse;
use flate2::read::GzDecoder; use chrono::{Datelike, Utc};
use geographiclib::Geodesic; use geographiclib::Geodesic;
use image::codecs::png::PngEncoder; use image::codecs::png::PngEncoder;
use image::{Rgba, RgbaImage}; use image::{Rgba, RgbaImage};
@ -16,19 +17,13 @@ use serde::{Deserialize, Serialize};
use std::collections::HashMap; use std::collections::HashMap;
use std::f64::consts::PI; use std::f64::consts::PI;
use std::fmt::Debug; use std::fmt::Debug;
use std::{fs, io}; use std::io;
use std::io::{Cursor, ErrorKind}; use std::io::ErrorKind;
use std::num::TryFromIntError;
use std::sync::Arc; use std::sync::Arc;
use chrono::{Datelike, Utc};
use tokio::io::AsyncReadExt;
use tracing::{debug, info_span}; use tracing::{debug, info_span};
use wxbox_ar2::sites::wsr88d::{SITES, Wsr88dSite}; use wxbox_ar2::sites::wsr88d::{SITES, Wsr88dSite};
use wxbox_ar2::{MomentValue, Radial, Scan, Sweep, parse}; use wxbox_ar2::{MomentValue, Radial, Scan, Sweep, parse};
use wxbox_grib2::GribMessage; use wxbox_pal::ColorPalette;
use wxbox_grib2::wgs84::LatLong;
use wxbox_pal::{ColorPalette, Palette};
use crate::nexrad_list_response::ListBucketResult;
pub type NexradDataCache = Cache<DataId, Arc<wxbox_ar2::Scan>>; pub type NexradDataCache = Cache<DataId, Arc<wxbox_ar2::Scan>>;
pub type NexradTileCache = Cache<TileId, Arc<Vec<u8>>>; pub type NexradTileCache = Cache<TileId, Arc<Vec<u8>>>;
@ -92,8 +87,14 @@ pub async fn nexrad_handler(
// we know we need to build the tile, so let's do that now // 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 // it also returns it, so we can conveniently return it right now
let pixel_data = let pixel_data = render_to_png(
render_to_png(state.nexrad_tile_cache.clone(), data, tile_id, &site, ds.clone()).await?; state.nexrad_tile_cache.clone(),
data,
tile_id,
&site,
ds.clone(),
)
.await?;
Ok(( Ok((
[(header::CONTENT_TYPE, "image/png")], [(header::CONTENT_TYPE, "image/png")],
@ -104,23 +105,24 @@ pub async fn nexrad_handler(
async fn load_nexrad_data( async fn load_nexrad_data(
cache: NexradDataCache, cache: NexradDataCache,
data_id: DataId, data_id: DataId,
data_source: NexradDataSource, _data_source: NexradDataSource,
site: &str, site: &str,
) -> anyhow::Result<Arc<Scan>> { ) -> anyhow::Result<Arc<Scan>> {
let _load_span = info_span!("load_nexrad_data"); let _load_span = info_span!("load_nexrad_data");
let mut searchdate = Utc::now(); let mut searchdate = Utc::now();
let client = reqwest::Client::new(); let client = reqwest::Client::new();
let mut url; let url;
let mut days_went_back = 0; let mut days_went_back = 0;
'outer: loop { 'outer: loop {
let year = searchdate.year(); let year = searchdate.year();
let month = searchdate.month(); let month = searchdate.month();
let day = searchdate.day(); let day = searchdate.day();
let listing_url = format!("https://noaa-nexrad-level2.s3.amazonaws.com/?list-type=2&delimiter=/&prefix={year}/{month:02}/{day:02}/{site}/"); let listing_url = format!(
"https://noaa-nexrad-level2.s3.amazonaws.com/?list-type=2&delimiter=/&prefix={year}/{month:02}/{day:02}/{site}/"
);
debug!("downloading listing from {}", listing_url); debug!("downloading listing from {}", listing_url);
let r = client.get(listing_url).send().await?; let r = client.get(listing_url).send().await?;
if !r.status().is_success() { if !r.status().is_success() {
@ -135,18 +137,20 @@ async fn load_nexrad_data(
// fake. continue // fake. continue
continue; continue;
} else { } else {
debug!("nexrad: found l2 file for {} date {}", site, file.LastModified); debug!(
"nexrad: found l2 file for {} date {}",
site, file.LastModified
);
url = format!("https://noaa-nexrad-level2.s3.amazonaws.com/{}", file.Key); url = format!("https://noaa-nexrad-level2.s3.amazonaws.com/{}", file.Key);
break 'outer; break 'outer;
} }
} }
} }
searchdate -= chrono::Duration::days(1); searchdate -= chrono::Duration::days(1);
days_went_back += 1; days_went_back += 1;
if days_went_back > 7 { if days_went_back > 7 {
bail!("no recent radar data"); bail!("no recent radar data");
} }
} }
let client = reqwest::Client::new(); let client = reqwest::Client::new();
@ -169,15 +173,6 @@ async fn load_nexrad_data(
const TWO_PI: f64 = PI * 2.0; const TWO_PI: f64 = PI * 2.0;
const HALF_PI: f64 = PI / 2.0; const HALF_PI: f64 = PI / 2.0;
const A: f64 = 6_378.1370; // km
const B: f64 = 6_356.7523; // km
fn radius_at_latitude(phi: f64) -> f64 {
let top = (A.powi(2) * phi.cos()).powi(2) + (B.powi(2) * phi.sin()).powi(2);
let bottom = (A * phi.cos()).powi(2) + (B * phi.sin()).powi(2);
(top / bottom).sqrt()
}
fn calculate_inverse( fn calculate_inverse(
tile_x_times_tilesize: f64, tile_x_times_tilesize: f64,
tile_y_times_tilesize: f64, tile_y_times_tilesize: f64,
@ -241,7 +236,7 @@ async fn render_to_png(
let tile_x_times_tilesize = tile_id.x 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; let tile_y_times_tilesize = tile_id.y as f64 * tile_id.size as f64;
let first_sweep = data.sweeps.get(0).unwrap(); let first_sweep = data.sweeps.first().unwrap();
let g = Geodesic::wgs84(); let g = Geodesic::wgs84();
let radar = SITES.sites.get(site).unwrap(); let radar = SITES.sites.get(site).unwrap();
@ -284,7 +279,7 @@ async fn render_to_png(
let data = &reflectivity.values; let data = &reflectivity.values;
if gate > data.len() || gate < 0 { if gate > data.len() {
return colorize(None, &data_source).unwrap_or(Rgba::from([0, 0, 0, 0])); return colorize(None, &data_source).unwrap_or(Rgba::from([0, 0, 0, 0]));
} }
@ -296,7 +291,7 @@ async fn render_to_png(
), ),
}); });
return colorize(color, &data_source).unwrap_or(Rgba::from([0, 0, 0, 0])); colorize(color, &data_source).unwrap_or(Rgba::from([0, 0, 0, 0]))
}) })
.collect::<Vec<_>>() .collect::<Vec<_>>()
}) })
@ -338,12 +333,7 @@ fn colorize(
Some(MomentValue::RangeFolded) => Rgba([119, 0, 125, 255]), Some(MomentValue::RangeFolded) => Rgba([119, 0, 125, 255]),
Some(MomentValue::Value(value)) => { Some(MomentValue::Value(value)) => {
let color = wxbox_pal::parser::parse(&data_source.palette)?.colorize(value as f64); let color = wxbox_pal::parser::parse(&data_source.palette)?.colorize(value as f64);
Rgba([ Rgba([color.red, color.green, color.blue, color.alpha])
color.red,
color.green,
color.blue,
color.alpha,
])
} }
None => Rgba([0, 0, 0, 30]), None => Rgba([0, 0, 0, 30]),
}) })

View file

@ -3,16 +3,11 @@ use serde::Deserialize;
#[derive(Deserialize, Debug)] #[derive(Deserialize, Debug)]
#[allow(non_snake_case)] // xml #[allow(non_snake_case)] // xml
pub struct ListBucketResult { pub struct ListBucketResult {
pub Name: String,
pub Prefix: String,
pub KeyCount: usize,
pub MaxKeys: usize,
pub Contents: Option<Vec<ListBucketResultContents>>, pub Contents: Option<Vec<ListBucketResultContents>>,
} }
#[derive(Deserialize, Debug)] #[derive(Deserialize, Debug)]
#[allow(non_snake_case)] // xml #[allow(non_snake_case)] // xml
pub struct ListBucketResultContents { pub struct ListBucketResultContents {
pub Key: String, pub Key: String,
pub LastModified: String pub LastModified: String,
} }