feat: ssr
This commit is contained in:
parent
9a714a9323
commit
74d9647689
7 changed files with 60 additions and 13 deletions
15
Cargo.lock
generated
15
Cargo.lock
generated
|
@ -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",
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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()?;
|
||||||
|
|
|
@ -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"] }
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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?;
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue