tile rendering
This commit is contained in:
parent
8eca740213
commit
2dd910e919
|
@ -2,6 +2,7 @@
|
||||||
<module type="WEB_MODULE" version="4">
|
<module type="WEB_MODULE" version="4">
|
||||||
<component name="NewModuleRootManager">
|
<component name="NewModuleRootManager">
|
||||||
<content url="file://$MODULE_DIR$">
|
<content url="file://$MODULE_DIR$">
|
||||||
|
<sourceFolder url="file://$MODULE_DIR$/wxbox-pal/src" isTestSource="false" />
|
||||||
<sourceFolder url="file://$MODULE_DIR$/wxbox-tiler/src" isTestSource="false" />
|
<sourceFolder url="file://$MODULE_DIR$/wxbox-tiler/src" isTestSource="false" />
|
||||||
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
|
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
|
||||||
<excludeFolder url="file://$MODULE_DIR$/temp" />
|
<excludeFolder url="file://$MODULE_DIR$/temp" />
|
||||||
|
|
|
@ -2194,6 +2194,14 @@ dependencies = [
|
||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wxbox-pal"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"ordered-float",
|
||||||
|
"thiserror",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wxbox-tiler"
|
name = "wxbox-tiler"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
@ -2201,11 +2209,14 @@ dependencies = [
|
||||||
"actix-web",
|
"actix-web",
|
||||||
"flate2",
|
"flate2",
|
||||||
"grib",
|
"grib",
|
||||||
|
"mime",
|
||||||
"ordered-float",
|
"ordered-float",
|
||||||
|
"png",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"tikv-jemallocator",
|
"tikv-jemallocator",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
"wxbox-pal",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
[workspace]
|
[workspace]
|
||||||
resolver = "2"
|
resolver = "2"
|
||||||
members = ["wxbox-tiler"]
|
members = [ "wxbox-pal","wxbox-tiler"]
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
codegen-units = 1
|
codegen-units = 1
|
||||||
lto = "fat"
|
lto = "fat"
|
||||||
|
|
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,8 @@
|
||||||
|
[package]
|
||||||
|
name = "wxbox-pal"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
thiserror = "1"
|
||||||
|
ordered-float = "4"
|
|
@ -0,0 +1,40 @@
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
use ordered_float::OrderedFloat;
|
||||||
|
|
||||||
|
pub struct Palette {
|
||||||
|
pub colors: BTreeMap<OrderedFloat<f64>, Color>
|
||||||
|
}
|
||||||
|
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||||
|
pub struct Color {
|
||||||
|
pub red: u8,
|
||||||
|
pub green: u8,
|
||||||
|
pub blue: u8,
|
||||||
|
pub alpha: u8
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Color {
|
||||||
|
pub fn average(self, other: Color) -> Color {
|
||||||
|
Color {
|
||||||
|
red: (self.red + other.red) / 2,
|
||||||
|
green: (self.green + other.green) / 2,
|
||||||
|
blue: (self.blue + other.blue) / 2,
|
||||||
|
alpha: (self.alpha + other.alpha) / 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Palette {
|
||||||
|
pub fn color_for(&self, val: f64) -> Color {
|
||||||
|
if self.colors.is_empty() {
|
||||||
|
panic!("color_for() on empty palette");
|
||||||
|
} else {
|
||||||
|
let mut best_v: Option<Color> = None;
|
||||||
|
for (k, v) in &self.colors {
|
||||||
|
if val < **k {
|
||||||
|
best_v = Some(*v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
best_v.expect("color_for() on empty palette?")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -13,4 +13,7 @@ ordered-float = "4"
|
||||||
tikv-jemallocator = "0.6"
|
tikv-jemallocator = "0.6"
|
||||||
reqwest = "0.12"
|
reqwest = "0.12"
|
||||||
flate2 = "1"
|
flate2 = "1"
|
||||||
tokio = "1"
|
tokio = "1"
|
||||||
|
wxbox-pal = { version = "0.1", path = "../wxbox-pal" }
|
||||||
|
png = "0.17"
|
||||||
|
mime = "0.3.17"
|
|
@ -10,23 +10,26 @@ use thiserror::Error;
|
||||||
|
|
||||||
pub fn closest_key<V>(map: &BTreeMap<OrderedFloat<f64>, V>, val: f64) -> Option<OrderedFloat<f64>> {
|
pub fn closest_key<V>(map: &BTreeMap<OrderedFloat<f64>, V>, val: f64) -> Option<OrderedFloat<f64>> {
|
||||||
let mut r1 = map.range(OrderedFloat(val)..);
|
let mut r1 = map.range(OrderedFloat(val)..);
|
||||||
let mut r2 = map.range(..OrderedFloat(val));
|
let mut r2 = map.range(..=OrderedFloat(val));
|
||||||
let o1 = r1.next();
|
let o1 = r1.next();
|
||||||
let o2 = r2.next();
|
let o2 = r2.last();
|
||||||
match (o1, o2) {
|
match (o1, o2) {
|
||||||
(None, None) => None,
|
(None, None) => {
|
||||||
(Some(i), None) => Some(*i.0),
|
None
|
||||||
(None, Some(i)) => Some(*i.0),
|
},
|
||||||
|
(Some(i), None) => {
|
||||||
|
Some(*i.0)
|
||||||
|
},
|
||||||
|
(None, Some(i)) => {
|
||||||
|
Some(*i.0)
|
||||||
|
},
|
||||||
(Some(i1), Some(i2)) => {
|
(Some(i1), Some(i2)) => {
|
||||||
// abs(f - i)
|
// abs(f - i)
|
||||||
|
|
||||||
let i1_dist = (i1.0 - val).abs();
|
let i1_dist = (i1.0 - val).abs();
|
||||||
let i2_dist = (i2.0 - val).abs();
|
let i2_dist = (i2.0 - val).abs();
|
||||||
return Some(if i1_dist < i2_dist {
|
return Some(if i1_dist < i2_dist {
|
||||||
println!("closest_key() {} -> ({}, {}), ({}, {}) => {}", val, i1.0, i2.0, i1_dist, i2_dist, i1.0);
|
|
||||||
*i1.0
|
*i1.0
|
||||||
} else {
|
} else {
|
||||||
println!("closest_key() {} -> ({}, {}), ({}, {}) => {}", val, i1.0, i2.0, i1_dist, i2_dist, i2.0);
|
|
||||||
*i2.0
|
*i2.0
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -178,7 +181,11 @@ pub async fn map_grib2(i: Response) -> Result<GriddedLookupTable, GribMapError>
|
||||||
// prepare the map
|
// prepare the map
|
||||||
for ((lat, long), value) in values {
|
for ((lat, long), value) in values {
|
||||||
let lat = OrderedFloat(lat as f64);
|
let lat = OrderedFloat(lat as f64);
|
||||||
let long = OrderedFloat(long as f64);
|
let mut long = long as f64;
|
||||||
|
if long > 180.0 {
|
||||||
|
long -= 360.0
|
||||||
|
}
|
||||||
|
let long = OrderedFloat(long);
|
||||||
let value = value as f64;
|
let value = value as f64;
|
||||||
if !map.contains_key(&lat) {
|
if !map.contains_key(&lat) {
|
||||||
map.insert(lat, BTreeMap::new());
|
map.insert(lat, BTreeMap::new());
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
pub(crate) mod sources;
|
pub(crate) mod sources;
|
||||||
pub(crate) mod coords;
|
pub(crate) mod coords;
|
||||||
mod grib2;
|
mod grib2;
|
||||||
|
mod pixmap;
|
||||||
|
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::collections::{BTreeMap, HashMap};
|
use std::collections::{BTreeMap, HashMap};
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
|
||||||
|
|
||||||
|
use wxbox_pal::Color;
|
||||||
|
|
||||||
|
pub struct Pixmap {
|
||||||
|
data: [Color; 256 * 256]
|
||||||
|
}
|
||||||
|
impl Pixmap {
|
||||||
|
#[inline]
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
data: [Color { red: 0, green: 0, blue: 0, alpha: 0 }; 256 * 256]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[inline]
|
||||||
|
pub fn set(&mut self, x: usize, y: usize, color: Color) {
|
||||||
|
self.data[256 * x + y] = color;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn get(&self, x: usize, y: usize) -> Color {
|
||||||
|
self.data[256 * x + y]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_raw(self) -> [u8; 256 * 256 * 4] {
|
||||||
|
let mut output = [0u8; 256 * 256 * 4];
|
||||||
|
|
||||||
|
for (idx, color) in self.data.iter().enumerate() {
|
||||||
|
output[4 * idx] = color.red;
|
||||||
|
output[4 * idx + 1] = color.green;
|
||||||
|
output[4 * idx + 2] = color.blue;
|
||||||
|
output[4 * idx + 3] = color.alpha;
|
||||||
|
}
|
||||||
|
|
||||||
|
output
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,17 +1,26 @@
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::io::{BufReader, Cursor, Read};
|
use std::io::{BufReader, BufWriter, Cursor, Read};
|
||||||
use std::time::{SystemTime, UNIX_EPOCH};
|
use std::time::{SystemTime, UNIX_EPOCH};
|
||||||
use actix_web::{get, HttpResponse, web};
|
use actix_web::{get, HttpResponse, web};
|
||||||
use actix_web::http::StatusCode;
|
use actix_web::http::header::{CONTENT_TYPE, HeaderValue};
|
||||||
|
use actix_web::http::{header, StatusCode};
|
||||||
use actix_web::web::Data;
|
use actix_web::web::Data;
|
||||||
use flate2::read::GzDecoder;
|
use flate2::read::GzDecoder;
|
||||||
use grib::{GribError, SectionBody};
|
use grib::{GribError, SectionBody};
|
||||||
use grib::codetables::{CodeTable4_2, CodeTable4_3, Lookup};
|
use grib::codetables::{CodeTable4_2, CodeTable4_3, Lookup};
|
||||||
use ordered_float::OrderedFloat;
|
use ordered_float::OrderedFloat;
|
||||||
|
use png::{BitDepth, ColorType, Encoder};
|
||||||
|
use wxbox_pal::{Color, Palette};
|
||||||
use crate::{AppState, LutKey};
|
use crate::{AppState, LutKey};
|
||||||
use crate::coords::bounds;
|
use crate::coords::bounds;
|
||||||
use crate::grib2::{closest_key, lookup, map_grib2};
|
use crate::grib2::{closest_key, lookup, map_grib2};
|
||||||
|
use crate::pixmap::Pixmap;
|
||||||
|
|
||||||
|
macro_rules! color {
|
||||||
|
($v:expr,$r:expr,$g:expr,$b:expr) => { (OrderedFloat(f64::from($v)), Color { red: $r, green: $g, blue: $b, alpha: 255 }) };
|
||||||
|
($v:expr,$r:expr,$g:expr,$b:expr,$a:expr) => { (OrderedFloat(f64::from($v)), Color { red: $r, green: $g, blue: $b, alpha: $a }) };
|
||||||
|
}
|
||||||
|
|
||||||
#[get("/mrms_cref/{z}/{x}/{y}.png")]
|
#[get("/mrms_cref/{z}/{x}/{y}.png")]
|
||||||
async fn mrms_cref(path: web::Path<(i32, i32, i32)>, data: Data<AppState>) -> HttpResponse {
|
async fn mrms_cref(path: web::Path<(i32, i32, i32)>, data: Data<AppState>) -> HttpResponse {
|
||||||
|
@ -22,26 +31,21 @@ async fn mrms_cref(path: web::Path<(i32, i32, i32)>, data: Data<AppState>) -> Ht
|
||||||
let tile = crate::coords::Tile::new(x, y, z);
|
let tile = crate::coords::Tile::new(x, y, z);
|
||||||
|
|
||||||
let bbox = bounds(tile);
|
let bbox = bounds(tile);
|
||||||
|
|
||||||
println!("{:?}", bbox);
|
|
||||||
|
|
||||||
let mut needs_reload = false;
|
let mut needs_reload = false;
|
||||||
println!("{:?}", data.lut_cache_timestamps);
|
|
||||||
|
|
||||||
let lct_reader = data.lut_cache_timestamps.read().await;
|
let lct_reader = data.lut_cache_timestamps.read().await;
|
||||||
|
|
||||||
if let Some(t) = lct_reader.get(&LutKey::NoaaMrmsCref) {
|
if let Some(t) = lct_reader.get(&LutKey::NoaaMrmsCref) {
|
||||||
let dur = SystemTime::now().duration_since(*t).expect("time went backwards").as_secs();
|
let dur = SystemTime::now().duration_since(*t).expect("time went backwards").as_secs();
|
||||||
if dur > 120 {
|
if dur > 120 {
|
||||||
println!("cache busted, dur = {}", dur);
|
|
||||||
needs_reload = true;
|
needs_reload = true;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
println!("nothing in timestamp cache, redownloading");
|
|
||||||
needs_reload = true;
|
needs_reload = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::mem::drop(lct_reader);
|
drop(lct_reader);
|
||||||
|
|
||||||
if needs_reload {
|
if needs_reload {
|
||||||
let mut lct_writer = data.lut_cache_timestamps.write().await;
|
let mut lct_writer = data.lut_cache_timestamps.write().await;
|
||||||
|
@ -50,26 +54,97 @@ async fn mrms_cref(path: web::Path<(i32, i32, i32)>, data: Data<AppState>) -> Ht
|
||||||
let map = map_grib2(f).await.unwrap();
|
let map = map_grib2(f).await.unwrap();
|
||||||
lc_writer.insert(LutKey::NoaaMrmsCref, map);
|
lc_writer.insert(LutKey::NoaaMrmsCref, map);
|
||||||
lct_writer.insert(LutKey::NoaaMrmsCref, SystemTime::now());
|
lct_writer.insert(LutKey::NoaaMrmsCref, SystemTime::now());
|
||||||
println!("{:?}", data.lut_cache_timestamps);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(map) = data.lut_cache.read().await.get(&LutKey::NoaaMrmsCref) {
|
if let Some(map) = data.lut_cache.read().await.get(&LutKey::NoaaMrmsCref) {
|
||||||
let rows = map.range(closest_key(map, bbox.south).unwrap()..=closest_key(map, bbox.north).unwrap());
|
let closest_south = match closest_key(&map, bbox.south) {
|
||||||
println!("{}", map.len());
|
Some(c) => c,
|
||||||
println!("{:#?}", map.keys());
|
None => { eprintln!("gridded LUT is empty?"); return HttpResponse::new(StatusCode::NOT_FOUND); }
|
||||||
println!("N {}/{:?} S {}/{:?}", bbox.south, closest_key(map, bbox.south), bbox.north, closest_key(map, bbox.north));
|
};
|
||||||
let mut rowcount = 0;
|
let closest_north = match closest_key(&map, bbox.north) {
|
||||||
let mut colcount = 0;
|
Some(c) => c,
|
||||||
for row in rows {
|
None => { eprintln!("gridded LUT is empty?"); return HttpResponse::new(StatusCode::NOT_FOUND); }
|
||||||
rowcount += 1;
|
};
|
||||||
colcount = 0;
|
|
||||||
let cols = row.1.range(closest_key(row.1, bbox.west).unwrap()..=closest_key(row.1, bbox.east).unwrap());
|
let pal = Palette {
|
||||||
for col in cols {
|
colors: BTreeMap::from([
|
||||||
colcount += 1;
|
color!(-30, 165, 165, 165, 0),
|
||||||
|
color!(10, 0, 165, 255),
|
||||||
|
color!(20, 16, 255, 8),
|
||||||
|
color!(35, 251, 238, 0),
|
||||||
|
color!(50, 255, 0, 0),
|
||||||
|
color!(65, 247, 1, 249),
|
||||||
|
color!(75, 255, 255, 255),
|
||||||
|
color!(85, 184, 184, 184),
|
||||||
|
color!(95, 184, 184, 184)
|
||||||
|
]),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut image: Pixmap = Pixmap::new();
|
||||||
|
|
||||||
|
for (lat, row) in map.range(closest_south..=closest_north) {
|
||||||
|
let closest_west = match closest_key(&row, bbox.west) {
|
||||||
|
Some(c) => c,
|
||||||
|
None => { eprintln!("row LUT is empty?"); return HttpResponse::new(StatusCode::NOT_FOUND); }
|
||||||
|
};
|
||||||
|
let closest_east = match closest_key(&row, bbox.east) {
|
||||||
|
Some(c) => c,
|
||||||
|
None => { eprintln!("row LUT is empty?"); return HttpResponse::new(StatusCode::NOT_FOUND); }
|
||||||
|
};
|
||||||
|
for (long, gridpoint) in row.range(closest_west..=closest_east) {
|
||||||
|
let epsg_lon = **long;
|
||||||
|
let epsg_lat = **lat;
|
||||||
|
let wm_x = epsg_lon;
|
||||||
|
let wm_y = epsg_lat.tan().asinh();
|
||||||
|
let x = 0.5 + wm_x / 360.0;
|
||||||
|
let y = 0.5 - wm_y / (2.0 * std::f64::consts::PI);
|
||||||
|
let zoom = z;
|
||||||
|
let n = 2.0_f64.powi(zoom);
|
||||||
|
let x_tile = n * x;
|
||||||
|
let y_tile = n * y;
|
||||||
|
|
||||||
|
let x_pixel = x_tile.fract() * 256.0;
|
||||||
|
let y_pixel = y_tile.fract() * 256.0;
|
||||||
|
|
||||||
|
let x = x_pixel.trunc() as usize;
|
||||||
|
let y = y_pixel.trunc() as usize;
|
||||||
|
|
||||||
|
let mut color = pal.color_for(*gridpoint);
|
||||||
|
|
||||||
|
let existing = image.get(x, y);
|
||||||
|
if existing != (Color { red: 0, green: 0, blue: 0, alpha: 0 }) {
|
||||||
|
color = color.average(existing);
|
||||||
|
}
|
||||||
|
|
||||||
|
image.set(x, y, color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
println!("{}x{}", rowcount, colcount);
|
|
||||||
return HttpResponse::new(StatusCode::NOT_FOUND);
|
let mut buf: Vec<u8> = vec![];
|
||||||
|
// borrow checker insanity
|
||||||
|
{
|
||||||
|
let mut cur: Cursor<_> = Cursor::new(&mut buf);
|
||||||
|
let ref mut w = BufWriter::new(&mut cur);
|
||||||
|
let mut encoder = Encoder::new(w, 256, 256);
|
||||||
|
encoder.set_color(ColorType::Rgba);
|
||||||
|
encoder.set_depth(BitDepth::Eight);
|
||||||
|
encoder.set_source_gamma(png::ScaledFloat::from_scaled(45455));
|
||||||
|
encoder.set_source_gamma(png::ScaledFloat::new(1.0 / 2.2));
|
||||||
|
let source_chromaticities = png::SourceChromaticities::new(
|
||||||
|
(0.31270, 0.32900),
|
||||||
|
(0.64000, 0.33000),
|
||||||
|
(0.30000, 0.60000),
|
||||||
|
(0.15000, 0.06000)
|
||||||
|
);
|
||||||
|
encoder.set_source_chromaticities(source_chromaticities);
|
||||||
|
let mut writer = encoder.write_header().unwrap();
|
||||||
|
writer.write_image_data(&image.to_raw()).expect("failed to encode png");
|
||||||
|
writer.finish().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpResponse::Ok()
|
||||||
|
.insert_header(header::ContentType(mime::IMAGE_PNG))
|
||||||
|
.body(buf)
|
||||||
} else {
|
} else {
|
||||||
println!("gridded LUT not available for LutKey::NoaaMrmsCref while handling /mrms_cref/{z}/{x}/{y} tile request");
|
println!("gridded LUT not available for LutKey::NoaaMrmsCref while handling /mrms_cref/{z}/{x}/{y} tile request");
|
||||||
return HttpResponse::new(StatusCode::NOT_FOUND);
|
return HttpResponse::new(StatusCode::NOT_FOUND);
|
||||||
|
|
|
@ -30,5 +30,6 @@
|
||||||
"typescript-eslint": "^8.0.0",
|
"typescript-eslint": "^8.0.0",
|
||||||
"vite": "^5.0.3"
|
"vite": "^5.0.3"
|
||||||
},
|
},
|
||||||
"type": "module"
|
"type": "module",
|
||||||
|
"packageManager": "pnpm@9.6.0+sha512.38dc6fba8dba35b39340b9700112c2fe1e12f10b17134715a4aa98ccf7bb035e76fd981cf0bb384dfa98f8d6af5481c2bef2f4266a24bfa20c34eb7147ce0b5e"
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue