diff --git a/Cargo.lock b/Cargo.lock index 020fd37..e9044a0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2871,7 +2871,7 @@ dependencies = [ "log", "rustc-hash 1.1.0", "spirv", - "strum", + "strum 0.26.3", "termcolor", "thiserror 2.0.12", "unicode-xid", @@ -4556,9 +4556,15 @@ version = "0.26.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" dependencies = [ - "strum_macros", + "strum_macros 0.26.4", ] +[[package]] +name = "strum" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f64def088c51c9510a8579e3c5d67c65349dcf755e5479ad3d010aa6454e2c32" + [[package]] name = "strum_macros" version = "0.26.4" @@ -4572,6 +4578,19 @@ dependencies = [ "syn", ] +[[package]] +name = "strum_macros" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c77a8c5abcaf0f9ce05d62342b7d298c346515365c36b673df4ebe3ced01fde8" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn", +] + [[package]] name = "subtle" version = "2.6.1" @@ -6307,11 +6326,14 @@ dependencies = [ "rayon", "reqwest", "serde", + "strum 0.27.1", + "strum_macros 0.27.1", "tokio", "toml", "tracing", "tracing-subscriber", "wxbox-ar2", + "wxbox-common", "wxbox-grib2", "wxbox-pal", ] diff --git a/crates/client/assets/sw.js b/crates/client/assets/sw.js index 48f3fc9..2ee0e8b 100644 --- a/crates/client/assets/sw.js +++ b/crates/client/assets/sw.js @@ -1,12 +1,15 @@ +/* var cacheName = 'egui-template-pwa'; + */ +/* var filesToCache = [ './', './index.html', './eframe_template.js', './eframe_template_bg.wasm', ]; - -/* Start the service worker and cache all of the app's content */ +*/ +/* Start the service worker and cache all of the app's content *//* self.addEventListener('install', function (e) { e.waitUntil( caches.open(cacheName).then(function (cache) { @@ -14,12 +17,13 @@ self.addEventListener('install', function (e) { }) ); }); - -/* Serve cached content when offline */ +*/ +/* Serve cached content when offline *//* self.addEventListener('fetch', function (e) { e.respondWith( caches.match(e.request).then(function (response) { return response || fetch(e.request); }) ); -}); \ No newline at end of file +}); +*/ \ No newline at end of file diff --git a/crates/client/src/app.rs b/crates/client/src/app.rs index 35220bb..a123bf2 100644 --- a/crates/client/src/app.rs +++ b/crates/client/src/app.rs @@ -88,7 +88,7 @@ impl eframe::App for App { &mut self.selected_layer, ); } - //right_bar(ctx); + //right_bar(ctx, self); egui::CentralPanel::default().show(ctx, |ui| { ui.add(walkers::Map::new( diff --git a/crates/client/src/ui/shell/main_menu.rs b/crates/client/src/ui/shell/main_menu.rs index a56a9e3..c142129 100644 --- a/crates/client/src/ui/shell/main_menu.rs +++ b/crates/client/src/ui/shell/main_menu.rs @@ -14,6 +14,8 @@ use egui::{ use std::collections::HashMap; use std::sync::Arc; use tracing::debug; +use crate::map::sources::baselayers::BASELAYER_OSM; +use crate::map::sources::datalayers::{NOAA_MRMS_MERGED_CREF_QC_CONUS, NOAA_MRMS_MERGED_RHOHV_3KM_CONUS}; fn outer_rect(f: &Prepared) -> Rect { let content_rect = f.content_ui.min_rect(); @@ -102,6 +104,33 @@ pub fn main_menu( }, ); + let mut add = |id| { + layer_manager.active_layers.insert(0, ActiveLayer { + source_id: id, + layer_id: LayerId::new(), + visible: true, + }); + *add_open = false; + }; + + ui.collapsing("Baselayers", |ui| { + if ui.button("OpenStreetMap").clicked() { + add(BASELAYER_OSM.source_id); + } + }); + ui.collapsing("Datalayers", |ui| { + ui.collapsing("NOAA / MRMS", |ui| { + ui.collapsing("Continental US", |ui| { + for layer in &[&NOAA_MRMS_MERGED_CREF_QC_CONUS, &NOAA_MRMS_MERGED_RHOHV_3KM_CONUS] { + if ui.button(&layer.display_name).clicked() { + add(layer.source_id); + } + } + }); + }); + }); + + /* for (id, registered_layer) in &layer_manager.registered_sources { // ghost button @@ -221,6 +250,8 @@ pub fn main_menu( } frame.paint(ui); } + + */ }); } } diff --git a/crates/client/src/ui/shell/right_bar.rs b/crates/client/src/ui/shell/right_bar.rs index 86a857c..3cacaf6 100644 --- a/crates/client/src/ui/shell/right_bar.rs +++ b/crates/client/src/ui/shell/right_bar.rs @@ -1,7 +1,8 @@ use crate::ui::shell::bars::pane_header; -use egui::{Align, Direction, Frame, Layout, RichText}; +use egui::{Align, Direction, FontFamily, Frame, Layout, RichText, Ui}; +use crate::app::App; -pub fn right_bar(ctx: &egui::Context) { +pub fn right_bar(ctx: &egui::Context, app: &mut App) { let mut frame = Frame::side_top_panel(&ctx.style()); frame.inner_margin.top = 4; frame.inner_margin.left = 0; @@ -10,19 +11,67 @@ pub fn right_bar(ctx: &egui::Context) { egui::SidePanel::right("right_panel") .frame(frame) .show(ctx, |ui| { - pane_header( - ui, - "Properties", - Some(egui_phosphor::regular::INFO), - false, - |ui| {}, - ); + if let Some(selected_layer_id) = app.selected_layer { + if let Some(active_layer) = app.layer_manager.active_layers.iter().find(|u| u.layer_id == selected_layer_id) { + if let Some(source) = app.layer_manager.registered_sources.get(&active_layer.source_id) { + pane_header( + ui, + &source.display_name, + Some(source.type_hint.icon()), + false, + |ui| {} + ); - ui.with_layout(Layout::top_down_justified(Align::Center), |ui| { - ui.label( - RichText::new("Select a layer in the Layers panel to modify it's properties") - .italics(), + egui::Grid::new("properties") + .num_columns(2) + .spacing([40.0, 4.0]) + .striped(true) + .show(ui, |ui| { + let mut draw_row = |title, content: Box<dyn Fn(&mut Ui)>| { + ui.horizontal(|ui| { + ui.add_space(8.0); + ui.label( + RichText::new(title) + .family(FontFamily::Name("semibold".into())) + .strong(), + ); + }); + ui.horizontal(|ui| { + content(ui); + ui.add_space(ui.available_width().max(8.0)); + }); + ui.end_row(); + }; + draw_row("Source", Box::new(|ui: &mut Ui| { + ui.label(&source.source); + })); + draw_row("Coverage", Box::new(|ui: &mut Ui| { + ui.label(&source.location); + })); + + + + }); + } + + } + } else { + pane_header( + ui, + "Properties", + Some(egui_phosphor::regular::INFO), + false, + |ui| {}, ); - }); + + ui.horizontal_centered(|ui| { + ui.label( + RichText::new("Select a layer in the Layers panel to modify it's properties") + .italics(), + ); + }); + } + + }); } diff --git a/crates/common/src/lib.rs b/crates/common/src/lib.rs index 9feb723..fdd6f6a 100644 --- a/crates/common/src/lib.rs +++ b/crates/common/src/lib.rs @@ -1,3 +1,4 @@ +use std::fmt::Debug; use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize)] @@ -13,3 +14,34 @@ impl Default for TileRequestOptions { } } } + +#[derive(Deserialize, Serialize)] +pub struct GribTileMetadata { + pub message_time: GribMessageTimeMetadata, + pub source_configuration: Grib2DataSource +} +#[derive(Deserialize, Serialize)] +pub struct GribMessageTimeMetadata { + pub reference_time_significance: u8, + pub year: u16, + pub month: u8, + pub day: u8, + pub hour: u8, + pub minute: u8, + pub second: u8, +} +#[derive(Serialize, Deserialize, Clone)] +pub struct Grib2DataSource { + pub from: String, + pub needs_gzip: bool, + pub valid_for: usize, + pub palette: String, + pub missing: Option<f64>, + pub no_coverage: Option<f64>, + pub range_folded: Option<f64>, +} +impl Debug for Grib2DataSource { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "<grib2 data source>") + } +} \ No newline at end of file diff --git a/crates/tiler/Cargo.toml b/crates/tiler/Cargo.toml index 0292aa1..c122dcb 100644 --- a/crates/tiler/Cargo.toml +++ b/crates/tiler/Cargo.toml @@ -36,4 +36,9 @@ wxbox-pal = { version = "0.1", path = "../pal" } # nexrad geographiclib = "0.1" -rayon = "1.10" \ No newline at end of file +rayon = "1.10" +strum = "0.27" +strum_macros = "0.27" + +# meta +wxbox-common = { path = "../common" } \ No newline at end of file diff --git a/crates/tiler/config.toml b/crates/tiler/config.toml index be3318f..cc00c4c 100644 --- a/crates/tiler/config.toml +++ b/crates/tiler/config.toml @@ -15,7 +15,7 @@ Color: 80 128 128 128 missing = -99.0 no_coverage = -999.0 -[data.nexrad.kcrp_ref_test] +[data.nexrad.l2_reflectivity] from = "aaaa" palette = """ Color: 5 0x40 0xe8 0xe3 diff --git a/crates/tiler/src/grib2.rs b/crates/tiler/src/grib2.rs index c2c6e39..06e8273 100644 --- a/crates/tiler/src/grib2.rs +++ b/crates/tiler/src/grib2.rs @@ -18,6 +18,7 @@ use std::io; use std::io::{Cursor, ErrorKind}; use std::num::TryFromIntError; use std::sync::Arc; +use axum::Json; use tokio::io::AsyncReadExt; use tracing::{debug, info_span}; use wxbox_grib2::GribMessage; @@ -25,25 +26,48 @@ use wxbox_grib2::wgs84::LatLong; use wxbox_pal::{ColorPalette, Palette}; use rayon::iter::ParallelIterator; +use wxbox_common::{Grib2DataSource, GribMessageTimeMetadata, GribTileMetadata}; + pub type Grib2DataCache = Cache<DataId, Arc<GribMessage>>; pub type Grib2TileCache = Cache<TileId, Arc<Vec<u8>>>; pub type Grib2DataConfig = HashMap<String, Grib2DataSource>; -#[derive(Serialize, Deserialize, Clone)] -pub struct Grib2DataSource { - pub from: String, - pub needs_gzip: bool, - pub valid_for: usize, - pub palette: String, - pub missing: Option<f64>, - pub no_coverage: Option<f64>, - pub range_folded: Option<f64>, -} -impl Debug for Grib2DataSource { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "<grib2 data source>") - } +#[tracing::instrument(level = "info")] +pub async fn grib2_metadata( + Path((source)): Path<(String)>, + State(state): State<AppState>, +) -> Result<Json<wxbox_common::GribTileMetadata>, AppError> { + // is this even a valid data source? + let data_id = DataId { + source, + }; + let Some(ds) = state.config.data.grib2.get(&data_id.source) else { + return Err(anyhow!("invalid/unknown grib2 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.grib2_data_cache.contains_key(&data_id) { + // we don't, so let's start by starting a task for that + load_grib2_data(state.grib2_data_cache, data_id, ds.clone()).await? + } else { + state.grib2_data_cache.get(&data_id).await.unwrap() + }; + + Ok(Json(GribTileMetadata { + message_time: GribMessageTimeMetadata { + reference_time_significance: data.identification.reference_time_significance, + year: data.identification.year, + month: data.identification.month, + day: data.identification.day, + hour: data.identification.hour, + minute: data.identification.minute, + second: data.identification.second, + }, + source_configuration: ds.clone(), + })) } #[tracing::instrument(level = "info")] diff --git a/crates/tiler/src/main.rs b/crates/tiler/src/main.rs index 8f07a19..a8c99c3 100644 --- a/crates/tiler/src/main.rs +++ b/crates/tiler/src/main.rs @@ -5,7 +5,7 @@ mod nexrad; mod tiles; use crate::config::Config; -use crate::grib2::{Grib2DataCache, Grib2TileCache, grib2_handler}; +use crate::grib2::{Grib2DataCache, Grib2TileCache, grib2_handler, grib2_metadata}; use crate::nexrad::{NexradDataCache, NexradTileCache, nexrad_handler}; use crate::tiles::{DataId, TileId}; use axum::Router; @@ -60,6 +60,7 @@ async fn main() -> anyhow::Result<()> { let app = Router::new() .route("/grib2/{source}/{z}/{x}/{y}", get(grib2_handler)) + .route("/grib2/{source}/metadata", get(grib2_metadata)) .route("/nexrad/{source}/{z}/{x}/{y}", get(nexrad_handler)) .with_state(state);