feat: ssr
Some checks failed
Verify Latest Dependencies / Verify Latest Dependencies (push) Has been cancelled
build and test / wxbox - latest (push) Has been cancelled

This commit is contained in:
core 2025-05-15 07:14:41 -04:00
parent 9a714a9323
commit 74d9647689
Signed by: core
GPG key ID: FDBF740DADDCEECF
7 changed files with 60 additions and 13 deletions

15
Cargo.lock generated
View file

@ -4972,6 +4972,20 @@ dependencies = [
"tracing", "tracing",
] ]
[[package]]
name = "tower-http"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fdb0c213ca27a9f57ab69ddb290fd80d970922355b83ae380b395d3986b8a2e"
dependencies = [
"bitflags 2.9.0",
"bytes",
"http",
"pin-project-lite",
"tower-layer",
"tower-service",
]
[[package]] [[package]]
name = "tower-layer" name = "tower-layer"
version = "0.3.3" version = "0.3.3"
@ -6330,6 +6344,7 @@ dependencies = [
"strum_macros 0.27.1", "strum_macros 0.27.1",
"tokio", "tokio",
"toml", "toml",
"tower-http",
"tracing", "tracing",
"tracing-subscriber", "tracing-subscriber",
"wxbox-ar2", "wxbox-ar2",

View file

@ -31,6 +31,18 @@
img.onload = () => { img.onload = () => {
map?.addImage('radar-rect-gray', img); map?.addImage('radar-rect-gray', img);
}; };
map.addSource('kcys-bref-0.5', {
type: 'raster',
tiles: [
'http://localhost:3000/nexrad/base_reflectivity_halfdegree/KCYS/{z}/{x}/{y}@2x.png'
],
'tileSize': 512
});
map.addLayer({
id: 'kcys',
type: 'raster',
source: 'kcys-bref-0.5'
});
}); });
}); });
</script> </script>

View file

@ -87,6 +87,7 @@ pub fn parse(input: Vec<u8>) -> nexrad_data::result::Result<Scan> {
let mut vcp = None; let mut vcp = None;
let mut radials = vec![]; let mut radials = vec![];
println!("{:?}", file.header());
for mut record in file.records() { for mut record in file.records() {
if record.compressed() { if record.compressed() {
record = record.decompress()?; record = record.decompress()?;

View file

@ -7,6 +7,7 @@ edition = "2024"
# web server # web server
tokio = { version = "1", features = ["full"] } tokio = { version = "1", features = ["full"] }
axum = "0.8" axum = "0.8"
tower-http = { version = "0.6", features = ["cors"]}
# caching # caching
moka = { version = "0.12", features = ["future"] } moka = { version = "0.12", features = ["future"] }

View file

@ -15,8 +15,8 @@ Color: 80 128 128 128
missing = -99.0 missing = -99.0
no_coverage = -999.0 no_coverage = -999.0
[data.nexrad.l2_reflectivity] [data.nexrad.base_reflectivity_halfdegree]
from = "aaaa" from = "DS.p94r0"
palette = """ palette = """
Color: 5 0x40 0xe8 0xe3 Color: 5 0x40 0xe8 0xe3
Color: 10 0x26 0xa4 0xfa Color: 10 0x26 0xa4 0xfa

View file

@ -14,6 +14,8 @@ 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 tracing_subscriber::fmt::format::FmtSpan; use tracing_subscriber::fmt::format::FmtSpan;
use tracing_subscriber::util::SubscriberInitExt; use tracing_subscriber::util::SubscriberInitExt;
use wxbox_grib2::GribMessage; use wxbox_grib2::GribMessage;
@ -58,11 +60,16 @@ async fn main() -> anyhow::Result<()> {
config: Arc::new(config), config: Arc::new(config),
}; };
let cors = CorsLayer::new()
.allow_methods([Method::GET])
.allow_origin(Any);
let app = Router::new() let app = Router::new()
.route("/grib2/{source}/{z}/{x}/{y}", get(grib2_handler)) .route("/grib2/{source}/{z}/{x}/{y}", get(grib2_handler))
.route("/grib2/{source}/metadata", get(grib2_metadata)) .route("/grib2/{source}/metadata", get(grib2_metadata))
.route("/nexrad/{source}/{z}/{x}/{y}", get(nexrad_handler)) .route("/nexrad/{source}/{site}/{z}/{x}/{y}", get(nexrad_handler))
.with_state(state); .with_state(state)
.layer(cors);
let listener = tokio::net::TcpListener::bind("[::]:3000").await?; let listener = tokio::net::TcpListener::bind("[::]:3000").await?;
axum::serve(listener, app).await?; axum::serve(listener, app).await?;

View file

@ -16,7 +16,7 @@ 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::io; use std::{fs, io};
use std::io::{Cursor, ErrorKind}; use std::io::{Cursor, ErrorKind};
use std::num::TryFromIntError; use std::num::TryFromIntError;
use std::sync::Arc; use std::sync::Arc;
@ -46,7 +46,7 @@ impl Debug for NexradDataSource {
#[tracing::instrument(level = "info")] #[tracing::instrument(level = "info")]
pub async fn nexrad_handler( pub async fn nexrad_handler(
Path((source, z, x, y)): Path<(String, usize, usize, String)>, Path((source, site, z, x, y)): Path<(String, String, usize, usize, String)>,
State(state): State<AppState>, State(state): State<AppState>,
) -> Result<impl IntoResponse, AppError> { ) -> Result<impl IntoResponse, AppError> {
let mut y = y let mut y = y
@ -59,7 +59,7 @@ pub async fn nexrad_handler(
} }
let y: usize = y.parse()?; let y: usize = y.parse()?;
let tile_id = TileId { let tile_id = TileId {
source, source: format!("{source}/{site}"),
z, z,
x, x,
y, y,
@ -73,7 +73,7 @@ pub async fn nexrad_handler(
// is this even a valid data source? // is this even a valid data source?
let data_id = tile_id.data_id(); let data_id = tile_id.data_id();
let Some(ds) = state.config.data.nexrad.get(&data_id.source) else { let Some(ds) = state.config.data.nexrad.get(&source) else {
return Err(anyhow!("invalid/unknown nexrad state").into()); return Err(anyhow!("invalid/unknown nexrad state").into());
}; };
@ -82,7 +82,7 @@ pub async fn nexrad_handler(
// lets check if we have the raw data // lets check if we have the raw data
let data = if !state.nexrad_data_cache.contains_key(&data_id) { let data = if !state.nexrad_data_cache.contains_key(&data_id) {
// we don't, so let's start by starting a task for that // 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? load_nexrad_data(state.nexrad_data_cache, data_id, ds.clone(), &site).await?
} else { } else {
state.nexrad_data_cache.get(&data_id).await.unwrap() state.nexrad_data_cache.get(&data_id).await.unwrap()
}; };
@ -90,7 +90,7 @@ 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(state.nexrad_tile_cache.clone(), data, tile_id, ds.clone()).await?; render_to_png(state.nexrad_tile_cache.clone(), data, tile_id, &site, ds.clone()).await?;
Ok(( Ok((
[(header::CONTENT_TYPE, "image/png")], [(header::CONTENT_TYPE, "image/png")],
@ -102,10 +102,21 @@ async fn load_nexrad_data(
cache: NexradDataCache, cache: NexradDataCache,
data_id: DataId, data_id: DataId,
data_source: NexradDataSource, data_source: NexradDataSource,
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 bytes = DATA_BYTES.to_vec(); // todo: find the correct
let url = format!("https://noaa-nexrad-level2.s3.amazonaws.com/2025/05/15/KCYS/KCYS20250515_003010_V06");
let client = reqwest::Client::new();
let r = client.get(url).send().await?;
if !r.status().is_success() {
bail!("nexrad data failed to load: {}", r.status());
}
let bytes = r.bytes().await?.to_vec();
let data = Arc::new(parse(bytes)?); let data = Arc::new(parse(bytes)?);
@ -175,7 +186,7 @@ async fn render_to_png(
cache: NexradTileCache, cache: NexradTileCache,
data: Arc<Scan>, data: Arc<Scan>,
tile_id: TileId, tile_id: TileId,
site: &str,
data_source: NexradDataSource, data_source: NexradDataSource,
) -> anyhow::Result<Arc<Vec<u8>>> { ) -> anyhow::Result<Arc<Vec<u8>>> {
let span = info_span!("render_to_png"); let span = info_span!("render_to_png");
@ -192,7 +203,7 @@ async fn render_to_png(
let first_sweep = data.sweeps.get(0).unwrap(); let first_sweep = data.sweeps.get(0).unwrap();
let g = Geodesic::wgs84(); let g = Geodesic::wgs84();
let radar = SITES.sites.get("KCRP").unwrap(); let radar = SITES.sites.get(site).unwrap();
drop(prep_span); drop(prep_span);