From dd2f25a5e8df3776641296795a1ad56949e708b2 Mon Sep 17 00:00:00 2001 From: core Date: Fri, 16 May 2025 19:25:51 -0400 Subject: [PATCH] client work --- Cargo.lock | 3 ++ client/src/lib/Map.svelte | 12 ------ client/src/lib/ToolbarProductSelector.svelte | 41 +++++++++++++++++- client/src/routes/+page.server.ts | 9 +--- crates/tiler/Cargo.toml | 6 ++- crates/tiler/src/main.rs | 3 ++ crates/tiler/src/nexrad.rs | 45 +++++++++++++++++++- crates/tiler/src/nexrad_list_response.rs | 16 +++++++ 8 files changed, 110 insertions(+), 25 deletions(-) create mode 100644 crates/tiler/src/nexrad_list_response.rs diff --git a/Cargo.lock b/Cargo.lock index a55f660..55883de 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3663,6 +3663,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf763ab1c7a3aa408be466efc86efe35ed1bd3dd74173ed39d6b0d0a6f0ba148" dependencies = [ "memchr", + "serde", ] [[package]] @@ -6333,10 +6334,12 @@ version = "0.1.0" dependencies = [ "anyhow", "axum", + "chrono", "flate2", "geographiclib", "image", "moka", + "quick-xml 0.37.3", "rayon", "reqwest", "serde", diff --git a/client/src/lib/Map.svelte b/client/src/lib/Map.svelte index 4bd7ff3..b2038f0 100644 --- a/client/src/lib/Map.svelte +++ b/client/src/lib/Map.svelte @@ -38,18 +38,6 @@ } load_image('radar-rect-green'); load_image('radar-rect-red'); - 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' - }); }); }); diff --git a/client/src/lib/ToolbarProductSelector.svelte b/client/src/lib/ToolbarProductSelector.svelte index 8b5dffa..a375b74 100644 --- a/client/src/lib/ToolbarProductSelector.svelte +++ b/client/src/lib/ToolbarProductSelector.svelte @@ -24,6 +24,7 @@ }: Props = $props(); let idToNameCache: Record = $state({}); + let idToTileUrlCache: Record = $state({}); let currentlySelectingFor: string | null = $state(null); let currentlySelectingCategory: PrimaryLayer[] | null = $state(null); @@ -65,6 +66,25 @@ onclick={() => { selectedPrimaryLayer = data.id; idToNameCache[data.id] = data.layer; + + if (!map.getSource(`${selectedSite}-${data.id}`)) { + idToTileUrlCache[data.id] = data.tileUrl; + map.addSource(`${selectedSite}-${data.id}`, { + type: 'raster', + tiles: [ + data.tileUrl.replace('{site}', selectedSite) + ], + 'tileSize': 512 + }); + } + if (map.getLayer("data")) { + map.removeLayer('data'); + } + map.addLayer({ + id: 'data', + type: 'raster', + source: `${selectedSite}-${data.id}` + }); }}>{data.layer} {/each} @@ -145,10 +165,29 @@ // did we already have a site set? let alreadyHadSite = selectedSite != null; pickingSiteForCategory = false; - selectedSite = e.features[0].properties.id; + selectedSite = e.features[0].properties.icao; if (!alreadyHadSite) { selectedPrimaryLayer = null; open = true; + } else { + if (!map.getSource(`${selectedSite}-${selectedPrimaryLayer}`)) { + + map.addSource(`${selectedSite}-${selectedPrimaryLayer}`, { + type: 'raster', + tiles: [ + idToTileUrlCache[selectedPrimaryLayer].replace('{site}', selectedSite) + ], + 'tileSize': 512 + }); + } + if (map.getLayer("data")) { + map.removeLayer('data'); + } + map.addLayer({ + id: 'data', + type: 'raster', + source: `${selectedSite}-${selectedPrimaryLayer}` + }); } // preserve the layer the user had if they click while already selected. } }); diff --git a/client/src/routes/+page.server.ts b/client/src/routes/+page.server.ts index 5186602..c5a594b 100644 --- a/client/src/routes/+page.server.ts +++ b/client/src/routes/+page.server.ts @@ -37,15 +37,8 @@ export const load: PageServerLoad = async () => { layer: 'Super Resolution Base Reflectivity (Tilt 1)', type: 'raster', tileUrl: - 'https://tiler.weather.ax/grib2/noaa_mrms_composite_reflectivity_qcd/{z}/{x}/{y}.png' + 'http://localhost:3000/nexrad/base_reflectivity_halfdegree/{site}/{z}/{x}/{y}@2x.png' }, - { - id: '01JV7SEN0GFB76NR843B1GFW5N', - layer: 'Super Resolution Base Velocity (Tilt 1)', - type: 'raster', - tileUrl: - 'https://tiler.weather.ax/grib2/noaa_mrms_composite_reflectivity_qcd/{z}/{x}/{y}.png' - } ] } ]; diff --git a/crates/tiler/Cargo.toml b/crates/tiler/Cargo.toml index bfea8c3..949433c 100644 --- a/crates/tiler/Cargo.toml +++ b/crates/tiler/Cargo.toml @@ -14,7 +14,7 @@ moka = { version = "0.12", features = ["future"] } # logging tracing = "0.1" -tracing-subscriber = "0.3" +tracing-subscriber = { version = "0.3", features = ["env-filter"] } # error handling anyhow = "1" @@ -42,4 +42,6 @@ strum = "0.27" strum_macros = "0.27" # meta -wxbox-common = { path = "../common" } \ No newline at end of file +wxbox-common = { path = "../common" } +chrono = "0.4" +quick-xml = { version = "0.37", features = ["serialize"] } \ No newline at end of file diff --git a/crates/tiler/src/main.rs b/crates/tiler/src/main.rs index 4f380b7..91860ea 100644 --- a/crates/tiler/src/main.rs +++ b/crates/tiler/src/main.rs @@ -3,6 +3,7 @@ mod error; mod grib2; mod nexrad; mod tiles; +mod nexrad_list_response; use crate::config::Config; use crate::grib2::{Grib2DataCache, Grib2TileCache, grib2_handler, grib2_metadata}; @@ -16,6 +17,7 @@ use std::fmt::{Debug, Formatter}; use std::sync::Arc; use axum::http::Method; use tower_http::cors::{Any, CorsLayer}; +use tracing_subscriber::EnvFilter; use tracing_subscriber::fmt::format::FmtSpan; use tracing_subscriber::util::SubscriberInitExt; use wxbox_grib2::GribMessage; @@ -41,6 +43,7 @@ impl Debug for AppState { async fn main() -> anyhow::Result<()> { tracing_subscriber::fmt::fmt() .with_span_events(FmtSpan::CLOSE) + .with_env_filter(EnvFilter::from_default_env()) .init(); let config_file = args().nth(1).expect("usage: wxbox-tiler config.toml"); diff --git a/crates/tiler/src/nexrad.rs b/crates/tiler/src/nexrad.rs index 06522d8..70ed230 100644 --- a/crates/tiler/src/nexrad.rs +++ b/crates/tiler/src/nexrad.rs @@ -20,6 +20,7 @@ use std::{fs, io}; use std::io::{Cursor, ErrorKind}; use std::num::TryFromIntError; use std::sync::Arc; +use chrono::{Datelike, Utc}; use tokio::io::AsyncReadExt; use tracing::{debug, info_span}; use wxbox_ar2::sites::wsr88d::{SITES, Wsr88dSite}; @@ -27,6 +28,7 @@ use wxbox_ar2::{DATA_BYTES, MomentValue, Radial, Scan, Sweep, parse}; use wxbox_grib2::GribMessage; use wxbox_grib2::wgs84::LatLong; use wxbox_pal::{ColorPalette, Palette}; +use crate::nexrad_list_response::ListBucketResult; pub type NexradDataCache = Cache>; pub type NexradTileCache = Cache>>; @@ -49,6 +51,7 @@ pub async fn nexrad_handler( Path((source, site, z, x, y)): Path<(String, String, usize, usize, String)>, State(state): State, ) -> Result { + debug!("here"); let mut y = y .strip_suffix(".png") .ok_or(io::Error::new(ErrorKind::InvalidInput, "invalid"))?; @@ -106,10 +109,48 @@ async fn load_nexrad_data( ) -> anyhow::Result> { let _load_span = info_span!("load_nexrad_data"); - // todo: find the correct - let url = format!("https://noaa-nexrad-level2.s3.amazonaws.com/2025/05/15/KCYS/KCYS20250515_003010_V06"); + let mut searchdate = Utc::now(); + let client = reqwest::Client::new(); + let mut url; + let mut days_went_back = 0; + + 'outer: loop { + let year = searchdate.year(); + let month = searchdate.month(); + 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}/"); + debug!("downloading listing from {}", listing_url); + let r = client.get(listing_url).send().await?; + if !r.status().is_success() { + bail!("nexrad data failed to load: {}", r.status()); + } + + let r: ListBucketResult = quick_xml::de::from_str(&r.text().await?)?; + + if let Some(contents) = r.Contents { + for file in contents.iter().rev() { + if file.Key.ends_with("_MDM") { + // fake. continue + continue; + } else { + debug!("nexrad: found l2 file for {} date {}", site, file.LastModified); + url = format!("https://noaa-nexrad-level2.s3.amazonaws.com/{}", file.Key); + break 'outer; + } + } + } + searchdate -= chrono::Duration::days(1); + days_went_back += 1; + if days_went_back > 7 { + bail!("no recent radar data"); + } + + } + + let client = reqwest::Client::new(); + debug!("downloading l2 file {}", url); let r = client.get(url).send().await?; if !r.status().is_success() { diff --git a/crates/tiler/src/nexrad_list_response.rs b/crates/tiler/src/nexrad_list_response.rs new file mode 100644 index 0000000..16ed89a --- /dev/null +++ b/crates/tiler/src/nexrad_list_response.rs @@ -0,0 +1,16 @@ +use serde::Deserialize; + +#[derive(Deserialize, Debug)] +pub struct ListBucketResult { + pub Name: String, + pub Prefix: String, + pub KeyCount: usize, + pub MaxKeys: usize, + + pub Contents: Option>, +} +#[derive(Deserialize, Debug)] +pub struct ListBucketResultContents { + pub Key: String, + pub LastModified: String +} \ No newline at end of file