client work
This commit is contained in:
parent
d862865830
commit
dd2f25a5e8
8 changed files with 110 additions and 25 deletions
3
Cargo.lock
generated
3
Cargo.lock
generated
|
@ -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",
|
||||
|
|
|
@ -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'
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
}: Props = $props();
|
||||
|
||||
let idToNameCache: Record<string, string> = $state({});
|
||||
let idToTileUrlCache: Record<string, string> = $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}</DropdownMenu.Item
|
||||
>
|
||||
{/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.
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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'
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
|
|
@ -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" }
|
||||
wxbox-common = { path = "../common" }
|
||||
chrono = "0.4"
|
||||
quick-xml = { version = "0.37", features = ["serialize"] }
|
|
@ -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");
|
||||
|
|
|
@ -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<DataId, Arc<wxbox_ar2::Scan>>;
|
||||
pub type NexradTileCache = Cache<TileId, Arc<Vec<u8>>>;
|
||||
|
@ -49,6 +51,7 @@ pub async fn nexrad_handler(
|
|||
Path((source, site, z, x, y)): Path<(String, String, usize, usize, String)>,
|
||||
State(state): State<AppState>,
|
||||
) -> Result<impl IntoResponse, AppError> {
|
||||
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<Arc<Scan>> {
|
||||
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() {
|
||||
|
|
16
crates/tiler/src/nexrad_list_response.rs
Normal file
16
crates/tiler/src/nexrad_list_response.rs
Normal file
|
@ -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<Vec<ListBucketResultContents>>,
|
||||
}
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct ListBucketResultContents {
|
||||
pub Key: String,
|
||||
pub LastModified: String
|
||||
}
|
Loading…
Add table
Reference in a new issue