tile rendering

This commit is contained in:
core 2024-10-19 00:06:56 -04:00
parent 8eca740213
commit 2dd910e919
Signed by: core
GPG Key ID: FDBF740DADDCEECF
14 changed files with 220 additions and 36 deletions

View File

@ -2,6 +2,7 @@
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager">
<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" />
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
<excludeFolder url="file://$MODULE_DIR$/temp" />

BIN
10.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

11
Cargo.lock generated
View File

@ -2194,6 +2194,14 @@ dependencies = [
"memchr",
]
[[package]]
name = "wxbox-pal"
version = "0.1.0"
dependencies = [
"ordered-float",
"thiserror",
]
[[package]]
name = "wxbox-tiler"
version = "0.1.0"
@ -2201,11 +2209,14 @@ dependencies = [
"actix-web",
"flate2",
"grib",
"mime",
"ordered-float",
"png",
"reqwest",
"thiserror",
"tikv-jemallocator",
"tokio",
"wxbox-pal",
]
[[package]]

View File

@ -1,6 +1,6 @@
[workspace]
resolver = "2"
members = ["wxbox-tiler"]
members = [ "wxbox-pal","wxbox-tiler"]
[profile.release]
codegen-units = 1

Binary file not shown.

8
wxbox-pal/Cargo.toml Normal file
View File

@ -0,0 +1,8 @@
[package]
name = "wxbox-pal"
version = "0.1.0"
edition = "2021"
[dependencies]
thiserror = "1"
ordered-float = "4"

40
wxbox-pal/src/lib.rs Normal file
View File

@ -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?")
}
}
}

View File

@ -14,3 +14,6 @@ tikv-jemallocator = "0.6"
reqwest = "0.12"
flate2 = "1"
tokio = "1"
wxbox-pal = { version = "0.1", path = "../wxbox-pal" }
png = "0.17"
mime = "0.3.17"

View File

@ -10,23 +10,26 @@ use thiserror::Error;
pub fn closest_key<V>(map: &BTreeMap<OrderedFloat<f64>, V>, val: f64) -> Option<OrderedFloat<f64>> {
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 o2 = r2.next();
let o2 = r2.last();
match (o1, o2) {
(None, None) => None,
(Some(i), None) => Some(*i.0),
(None, Some(i)) => Some(*i.0),
(None, None) => {
None
},
(Some(i), None) => {
Some(*i.0)
},
(None, Some(i)) => {
Some(*i.0)
},
(Some(i1), Some(i2)) => {
// abs(f - i)
let i1_dist = (i1.0 - val).abs();
let i2_dist = (i2.0 - val).abs();
return Some(if i1_dist < i2_dist {
println!("closest_key() {} -> ({}, {}), ({}, {}) => {}", val, i1.0, i2.0, i1_dist, i2_dist, i1.0);
*i1.0
} else {
println!("closest_key() {} -> ({}, {}), ({}, {}) => {}", val, i1.0, i2.0, i1_dist, i2_dist, i2.0);
*i2.0
})
}
@ -178,7 +181,11 @@ pub async fn map_grib2(i: Response) -> Result<GriddedLookupTable, GribMapError>
// prepare the map
for ((lat, long), value) in values {
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;
if !map.contains_key(&lat) {
map.insert(lat, BTreeMap::new());

View File

@ -1,6 +1,7 @@
pub(crate) mod sources;
pub(crate) mod coords;
mod grib2;
mod pixmap;
use std::borrow::Cow;
use std::collections::{BTreeMap, HashMap};

37
wxbox-tiler/src/pixmap.rs Normal file
View File

@ -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
}
}

View File

@ -1,17 +1,26 @@
use std::borrow::Cow;
use std::collections::BTreeMap;
use std::io::{BufReader, Cursor, Read};
use std::io::{BufReader, BufWriter, Cursor, Read};
use std::time::{SystemTime, UNIX_EPOCH};
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 flate2::read::GzDecoder;
use grib::{GribError, SectionBody};
use grib::codetables::{CodeTable4_2, CodeTable4_3, Lookup};
use ordered_float::OrderedFloat;
use png::{BitDepth, ColorType, Encoder};
use wxbox_pal::{Color, Palette};
use crate::{AppState, LutKey};
use crate::coords::bounds;
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")]
async fn mrms_cref(path: web::Path<(i32, i32, i32)>, data: Data<AppState>) -> HttpResponse {
@ -23,25 +32,20 @@ async fn mrms_cref(path: web::Path<(i32, i32, i32)>, data: Data<AppState>) -> Ht
let bbox = bounds(tile);
println!("{:?}", bbox);
let mut needs_reload = false;
println!("{:?}", data.lut_cache_timestamps);
let lct_reader = data.lut_cache_timestamps.read().await;
if let Some(t) = lct_reader.get(&LutKey::NoaaMrmsCref) {
let dur = SystemTime::now().duration_since(*t).expect("time went backwards").as_secs();
if dur > 120 {
println!("cache busted, dur = {}", dur);
needs_reload = true;
}
} else {
println!("nothing in timestamp cache, redownloading");
needs_reload = true;
}
std::mem::drop(lct_reader);
drop(lct_reader);
if needs_reload {
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();
lc_writer.insert(LutKey::NoaaMrmsCref, map);
lct_writer.insert(LutKey::NoaaMrmsCref, SystemTime::now());
println!("{:?}", data.lut_cache_timestamps);
}
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());
println!("{}", map.len());
println!("{:#?}", map.keys());
println!("N {}/{:?} S {}/{:?}", bbox.south, closest_key(map, bbox.south), bbox.north, closest_key(map, bbox.north));
let mut rowcount = 0;
let mut colcount = 0;
for row in rows {
rowcount += 1;
colcount = 0;
let cols = row.1.range(closest_key(row.1, bbox.west).unwrap()..=closest_key(row.1, bbox.east).unwrap());
for col in cols {
colcount += 1;
let closest_south = match closest_key(&map, bbox.south) {
Some(c) => c,
None => { eprintln!("gridded LUT is empty?"); return HttpResponse::new(StatusCode::NOT_FOUND); }
};
let closest_north = match closest_key(&map, bbox.north) {
Some(c) => c,
None => { eprintln!("gridded LUT is empty?"); return HttpResponse::new(StatusCode::NOT_FOUND); }
};
let pal = Palette {
colors: BTreeMap::from([
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 {
println!("gridded LUT not available for LutKey::NoaaMrmsCref while handling /mrms_cref/{z}/{x}/{y} tile request");
return HttpResponse::new(StatusCode::NOT_FOUND);

View File

@ -30,5 +30,6 @@
"typescript-eslint": "^8.0.0",
"vite": "^5.0.3"
},
"type": "module"
"type": "module",
"packageManager": "pnpm@9.6.0+sha512.38dc6fba8dba35b39340b9700112c2fe1e12f10b17134715a4aa98ccf7bb035e76fd981cf0bb384dfa98f8d6af5481c2bef2f4266a24bfa20c34eb7147ce0b5e"
}