tile rendering
This commit is contained in:
parent
8eca740213
commit
2dd910e919
|
@ -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" />
|
||||
|
|
|
@ -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]]
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[workspace]
|
||||
resolver = "2"
|
||||
members = ["wxbox-tiler"]
|
||||
members = [ "wxbox-pal","wxbox-tiler"]
|
||||
|
||||
[profile.release]
|
||||
codegen-units = 1
|
||||
|
|
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?")
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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"
|
|
@ -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());
|
||||
|
|
|
@ -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};
|
||||
|
|
|
@ -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::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);
|
||||
|
|
|
@ -30,5 +30,6 @@
|
|||
"typescript-eslint": "^8.0.0",
|
||||
"vite": "^5.0.3"
|
||||
},
|
||||
"type": "module"
|
||||
"type": "module",
|
||||
"packageManager": "pnpm@9.6.0+sha512.38dc6fba8dba35b39340b9700112c2fe1e12f10b17134715a4aa98ccf7bb035e76fd981cf0bb384dfa98f8d6af5481c2bef2f4266a24bfa20c34eb7147ce0b5e"
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue