ci workflow
Some checks are pending
build and test / wxbox - latest (push) Waiting to run

This commit is contained in:
core 2025-03-04 20:51:33 -05:00
parent 2927361680
commit 2a2f46a5ff
Signed by: core
GPG key ID: FDBF740DADDCEECF
20 changed files with 453 additions and 309 deletions

26
.forgejo/workflows/ci.yml Normal file
View file

@ -0,0 +1,26 @@
name: build and test
on:
push:
pull_request:
env:
CARGO_TERM_COLOR: always
jobs:
build_and_test:
name: wxbox - latest
runs-on: ubuntu-latest
strategy:
matrix:
toolchain:
- stable
- beta
- nightly
steps:
- uses: actions/checkout@v4
- run: rustup update ${{ matrix.toolchain }} && rustup default ${{ matrix.toolchain }}
- run: cargo build
- run: cargo test
- run: cargo clippy
- run: cargo fmt --check

View file

@ -1,7 +0,0 @@
on: [push]
jobs:
test:
runs-on: docker
steps:
- run: echo All Good

View file

@ -1,6 +1,6 @@
use std::io;
use image::ImageError; use image::ImageError;
use png::DecodingError; use png::DecodingError;
use std::io;
use thiserror::Error; use thiserror::Error;
#[derive(Error, Debug)] #[derive(Error, Debug)]
@ -38,5 +38,5 @@ pub enum GribError {
#[error("image error")] #[error("image error")]
PNGImageError(#[from] DecodingError), PNGImageError(#[from] DecodingError),
#[error("image error")] #[error("image error")]
ImageError(#[from] ImageError) ImageError(#[from] ImageError),
} }

View file

@ -2,16 +2,18 @@ pub mod error;
mod nommer; mod nommer;
pub mod wgs84; pub mod wgs84;
use crate::error::GribError;
use crate::nommer::NomReader;
use crate::wgs84::LatLong;
use crate::LatLongVectorRelativity::{EasterlyAndNortherly, IncreasingXY};
use image::codecs::png::PngDecoder;
use image::{
DynamicImage, GenericImageView, ImageBuffer, ImageDecoder, ImageFormat, ImageReader, Luma,
};
use std::fmt::{Debug, Formatter}; use std::fmt::{Debug, Formatter};
use std::io::{Cursor, Read}; use std::io::{Cursor, Read};
use std::time::Instant; use std::time::Instant;
use image::codecs::png::PngDecoder;
use image::{DynamicImage, GenericImageView, ImageBuffer, ImageDecoder, ImageFormat, ImageReader, Luma};
use tracing::{debug, warn}; use tracing::{debug, warn};
use crate::error::GribError;
use crate::LatLongVectorRelativity::{EasterlyAndNortherly, IncreasingXY};
use crate::nommer::NomReader;
use crate::wgs84::LatLong;
pub const INDICATOR: u32 = u32::from_be_bytes(*b"GRIB"); pub const INDICATOR: u32 = u32::from_be_bytes(*b"GRIB");
pub const EDITION: u8 = 2; pub const EDITION: u8 = 2;
@ -59,9 +61,11 @@ impl GribMessage {
loop { loop {
let length = match nommer.read_u32() { let length = match nommer.read_u32() {
Ok(l) => l, Ok(l) => l,
Err(e) => return match e.kind() { Err(e) => {
std::io::ErrorKind::UnexpectedEof => break, return match e.kind() {
_ => return Err(e.into()) std::io::ErrorKind::UnexpectedEof => break,
_ => return Err(e.into()),
}
} }
}; };
@ -74,22 +78,22 @@ impl GribMessage {
match section_number { match section_number {
1 => { 1 => {
identification = Some(IdentificationSection::parse(length, &mut nommer)?); identification = Some(IdentificationSection::parse(length, &mut nommer)?);
}, }
3 => { 3 => {
grid_definition = Some(GridDefinitionSection::parse(length, &mut nommer)?); grid_definition = Some(GridDefinitionSection::parse(length, &mut nommer)?);
}, }
4 => { 4 => {
product_definition = Some(ProductDefinitionSection::parse(length, &mut nommer)?); product_definition =
}, Some(ProductDefinitionSection::parse(length, &mut nommer)?);
}
5 => { 5 => {
data_representation = Some(DataRepresentationSection::parse(length, &mut nommer)?); data_representation =
}, Some(DataRepresentationSection::parse(length, &mut nommer)?);
}
6 => { 6 => {
bitmap = Some(BitmapSection::parse(length, &mut nommer)?); bitmap = Some(BitmapSection::parse(length, &mut nommer)?);
}, }
7 => { 7 => data = Some(DataSection::parse(length, &mut nommer)?),
data = Some(DataSection::parse(length, &mut nommer)?)
},
_ => { _ => {
let _ = nommer.read_n((length - 5) as usize); let _ = nommer.read_n((length - 5) as usize);
warn!("unimplemented section # {} len {}", section_number, length) warn!("unimplemented section # {} len {}", section_number, length)
@ -100,7 +104,8 @@ impl GribMessage {
let identification = identification.ok_or(GribError::MissingIdentification)?; let identification = identification.ok_or(GribError::MissingIdentification)?;
let grid_definition = grid_definition.ok_or(GribError::MissingGridDefinition)?; let grid_definition = grid_definition.ok_or(GribError::MissingGridDefinition)?;
let product_definition = product_definition.ok_or(GribError::MissingProductDefinition)?; let product_definition = product_definition.ok_or(GribError::MissingProductDefinition)?;
let mut data_representation = data_representation.ok_or(GribError::MissingDataRepresentation)?; let mut data_representation =
data_representation.ok_or(GribError::MissingDataRepresentation)?;
let bitmap = bitmap.ok_or(GribError::MissingBitmap)?; let bitmap = bitmap.ok_or(GribError::MissingBitmap)?;
let data = data.ok_or(GribError::MissingData)?; let data = data.ok_or(GribError::MissingData)?;
@ -119,14 +124,16 @@ impl GribMessage {
product_definition, product_definition,
data_representation, data_representation,
bitmap, bitmap,
data data,
}) })
} }
pub fn value_for(&self, coord: LatLong) -> Option<f32> { pub fn value_for(&self, coord: LatLong) -> Option<f32> {
match self.grid_definition.image_coordinates(coord) { match self.grid_definition.image_coordinates(coord) {
Some((i, j)) => self.data_representation.get_image_coordinate(i as u32, j as u32), Some((i, j)) => self
None => None .data_representation
.get_image_coordinate(i as u32, j as u32),
None => None,
} }
} }
} }
@ -146,7 +153,7 @@ pub struct IdentificationSection {
pub second: u8, pub second: u8,
pub production_status: u8, pub production_status: u8,
pub processed_data_type: u8, pub processed_data_type: u8,
pub reserved: Vec<u8> pub reserved: Vec<u8>,
} }
impl IdentificationSection { impl IdentificationSection {
fn parse<R: Read>(length: u32, nommer: &mut NomReader<R>) -> Result<Self, GribError> { fn parse<R: Read>(length: u32, nommer: &mut NomReader<R>) -> Result<Self, GribError> {
@ -178,12 +185,11 @@ impl IdentificationSection {
second, second,
production_status, production_status,
processed_data_type, processed_data_type,
reserved reserved,
}) })
} }
} }
#[derive(Debug)] #[derive(Debug)]
pub struct GridDefinitionSection { pub struct GridDefinitionSection {
pub source: u8, pub source: u8,
@ -191,7 +197,7 @@ pub struct GridDefinitionSection {
pub length_of_optional_number_list: u8, pub length_of_optional_number_list: u8,
pub interpretation_of_number_list: u8, pub interpretation_of_number_list: u8,
pub grid_definition_template_number: u16, pub grid_definition_template_number: u16,
pub grid_definition: GridDefinition pub grid_definition: GridDefinition,
} }
impl GridDefinitionSection { impl GridDefinitionSection {
fn parse<R: Read>(length: u32, nommer: &mut NomReader<R>) -> Result<Self, GribError> { fn parse<R: Read>(length: u32, nommer: &mut NomReader<R>) -> Result<Self, GribError> {
@ -211,7 +217,6 @@ impl GridDefinitionSection {
let interpretation_of_number_list = nommer.read_u8()?; let interpretation_of_number_list = nommer.read_u8()?;
let grid_definition_template_number = nommer.read_u16()?; let grid_definition_template_number = nommer.read_u16()?;
let grid_definition = match grid_definition_template_number { let grid_definition = match grid_definition_template_number {
0 => GridDefinition::LatitudeLongitude(LatLongGrid::parse(length, nommer)?), 0 => GridDefinition::LatitudeLongitude(LatLongGrid::parse(length, nommer)?),
_ => return Err(GribError::UnsupportedGrid(grid_definition_template_number)), _ => return Err(GribError::UnsupportedGrid(grid_definition_template_number)),
@ -223,7 +228,7 @@ impl GridDefinitionSection {
length_of_optional_number_list, length_of_optional_number_list,
interpretation_of_number_list, interpretation_of_number_list,
grid_definition_template_number, grid_definition_template_number,
grid_definition grid_definition,
}) })
} }
@ -243,20 +248,25 @@ impl ProductDefinitionSection {
let number_of_coordinate_values_after_template = nommer.read_u16()?; let number_of_coordinate_values_after_template = nommer.read_u16()?;
let product_definition_template_number = nommer.read_u16()?; let product_definition_template_number = nommer.read_u16()?;
if number_of_coordinate_values_after_template != 0 { if number_of_coordinate_values_after_template != 0 {
return Err(GribError::ListOfNumbersNotSupported); return Err(GribError::ListOfNumbersNotSupported);
} }
let product_definition = match product_definition_template_number { let product_definition = match product_definition_template_number {
0 => ProductDefinition::HorizontalLayerAtPointInTime(HorizontalLayerInstantaneousProductDef::parse(length, nommer)?), 0 => ProductDefinition::HorizontalLayerAtPointInTime(
_ => return Err(GribError::UnsupportedProductDefTmpl(product_definition_template_number)) HorizontalLayerInstantaneousProductDef::parse(length, nommer)?,
),
_ => {
return Err(GribError::UnsupportedProductDefTmpl(
product_definition_template_number,
))
}
}; };
Ok(Self { Ok(Self {
number_of_coordinate_values_after_template, number_of_coordinate_values_after_template,
product_definition_template_number, product_definition_template_number,
product_definition product_definition,
}) })
} }
} }
@ -265,7 +275,7 @@ impl ProductDefinitionSection {
pub struct DataRepresentationSection { pub struct DataRepresentationSection {
pub number_of_data_points: u32, pub number_of_data_points: u32,
pub data_representation_template_number: u16, pub data_representation_template_number: u16,
pub data_representation_template: DataRepresentationTemplate pub data_representation_template: DataRepresentationTemplate,
} }
impl DataRepresentationSection { impl DataRepresentationSection {
fn parse<R: Read>(length: u32, nommer: &mut NomReader<R>) -> Result<Self, GribError> { fn parse<R: Read>(length: u32, nommer: &mut NomReader<R>) -> Result<Self, GribError> {
@ -273,14 +283,20 @@ impl DataRepresentationSection {
let data_representation_template_number = nommer.read_u16()?; let data_representation_template_number = nommer.read_u16()?;
let data_representation_template = match data_representation_template_number { let data_representation_template = match data_representation_template_number {
41 => DataRepresentationTemplate::GridpointPNG(GridpointPNGDataRepresentation::parse(length, nommer)?), 41 => DataRepresentationTemplate::GridpointPNG(GridpointPNGDataRepresentation::parse(
_ => return Err(GribError::UnsupportedDataRepresentationTemplate(data_representation_template_number)) length, nommer,
)?),
_ => {
return Err(GribError::UnsupportedDataRepresentationTemplate(
data_representation_template_number,
))
}
}; };
Ok(Self { Ok(Self {
number_of_data_points, number_of_data_points,
data_representation_template_number, data_representation_template_number,
data_representation_template data_representation_template,
}) })
} }
@ -294,17 +310,17 @@ impl DataRepresentationSection {
#[derive(Debug)] #[derive(Debug)]
pub enum DataRepresentationTemplate { pub enum DataRepresentationTemplate {
GridpointPNG(GridpointPNGDataRepresentation) GridpointPNG(GridpointPNGDataRepresentation),
} }
impl DataRepresentationTemplate { impl DataRepresentationTemplate {
fn load_data(&mut self, data: Vec<u8>) -> Result<(), GribError> { fn load_data(&mut self, data: Vec<u8>) -> Result<(), GribError> {
match self { match self {
Self::GridpointPNG(png) => png.load_data(data) Self::GridpointPNG(png) => png.load_data(data),
} }
} }
fn get_image_coordinate(&self, x: u32, y: u32) -> Option<f32> { fn get_image_coordinate(&self, x: u32, y: u32) -> Option<f32> {
match self { match self {
Self::GridpointPNG(png) => png.get_image_coordinate(x, y) Self::GridpointPNG(png) => png.get_image_coordinate(x, y),
} }
} }
} }
@ -317,7 +333,7 @@ pub struct GridpointPNGDataRepresentation {
pub depth: u8, pub depth: u8,
pub type_of_values: u8, pub type_of_values: u8,
pub image: Option<DynamicImage> pub image: Option<DynamicImage>,
} }
impl GridpointPNGDataRepresentation { impl GridpointPNGDataRepresentation {
fn parse<R: Read>(_length: u32, nommer: &mut NomReader<R>) -> Result<Self, GribError> { fn parse<R: Read>(_length: u32, nommer: &mut NomReader<R>) -> Result<Self, GribError> {
@ -332,7 +348,7 @@ impl GridpointPNGDataRepresentation {
decimal_scale_factor, decimal_scale_factor,
depth, depth,
type_of_values, type_of_values,
image: None image: None,
}) })
} }
fn load_data(&mut self, data: Vec<u8>) -> Result<(), GribError> { fn load_data(&mut self, data: Vec<u8>) -> Result<(), GribError> {
@ -348,51 +364,54 @@ impl GridpointPNGDataRepresentation {
fn get_image_coordinate(&self, x: u32, y: u32) -> Option<f32> { fn get_image_coordinate(&self, x: u32, y: u32) -> Option<f32> {
match &self.image { match &self.image {
Some(i) => { Some(i) => match self.depth {
match self.depth { 1 | 2 | 4 | 8 | 16 => {
1 | 2 | 4 | 8 | 16 => { if x >= i.width() || y >= i.height() {
if x >= i.width() || y >= i.height() { None
None } else {
} else { let datapoint = i.as_luma16().unwrap().get_pixel(x, y).0[0] as f32;
let datapoint = i.as_luma16().unwrap().get_pixel(x, y).0[0] as f32; let diff = datapoint * 2.0_f32.powi(self.binary_scale_factor as i32);
let diff = datapoint * 2.0_f32.powi(self.binary_scale_factor as i32); let dig_factor = 10_f32.powi(-(self.decimal_scale_factor as i32));
let dig_factor = 10_f32.powi(-(self.decimal_scale_factor as i32)); let value = (self.reference_value + diff) * dig_factor;
let value = (self.reference_value + diff) * dig_factor; Some(value)
Some(value) }
}
},
24 => {
if x >= i.width() || y >= i.height() {
None
} else {
let datapoint_channels = i.as_rgb8().unwrap().get_pixel(x, y).0;
let datapoint = u32::from_be_bytes([0, datapoint_channels[0], datapoint_channels[1], datapoint_channels[2]]) as f32;
let diff = datapoint * 2.0_f32.powi(self.binary_scale_factor as i32);
let dig_factor = 10_f32.powi(-(self.decimal_scale_factor as i32));
let value = (self.reference_value + diff) * dig_factor;
Some(value)
}
},
32 => {
if x >= i.width() || y >= i.height() {
None
} else {
let datapoint_channels = i.as_rgba8().unwrap().get_pixel(x, y).0;
let datapoint = u32::from_be_bytes(datapoint_channels) as f32;
let diff = datapoint * 2.0_f32.powi(self.binary_scale_factor as i32);
let dig_factor = 10_f32.powi(-(self.decimal_scale_factor as i32));
let value = (self.reference_value + diff) * dig_factor;
Some(value)
}
},
_ => panic!("unsupported bit depth")
} }
24 => {
if x >= i.width() || y >= i.height() {
None
} else {
let datapoint_channels = i.as_rgb8().unwrap().get_pixel(x, y).0;
let datapoint = u32::from_be_bytes([
0,
datapoint_channels[0],
datapoint_channels[1],
datapoint_channels[2],
]) as f32;
let diff = datapoint * 2.0_f32.powi(self.binary_scale_factor as i32);
let dig_factor = 10_f32.powi(-(self.decimal_scale_factor as i32));
let value = (self.reference_value + diff) * dig_factor;
Some(value)
}
}
32 => {
if x >= i.width() || y >= i.height() {
None
} else {
let datapoint_channels = i.as_rgba8().unwrap().get_pixel(x, y).0;
let datapoint = u32::from_be_bytes(datapoint_channels) as f32;
let diff = datapoint * 2.0_f32.powi(self.binary_scale_factor as i32);
let dig_factor = 10_f32.powi(-(self.decimal_scale_factor as i32));
let value = (self.reference_value + diff) * dig_factor;
Some(value)
}
}
_ => panic!("unsupported bit depth"),
}, },
None => None None => None,
} }
} }
} }
@ -400,7 +419,7 @@ impl GridpointPNGDataRepresentation {
#[derive(Debug)] #[derive(Debug)]
pub struct BitmapSection { pub struct BitmapSection {
pub indicator: u8, pub indicator: u8,
pub bitmap: Vec<u8> pub bitmap: Vec<u8>,
} }
impl BitmapSection { impl BitmapSection {
fn parse<R: Read>(length: u32, nommer: &mut NomReader<R>) -> Result<Self, GribError> { fn parse<R: Read>(length: u32, nommer: &mut NomReader<R>) -> Result<Self, GribError> {
@ -410,22 +429,17 @@ impl BitmapSection {
} }
let bitmap = nommer.read_n((length - 6) as usize)?; let bitmap = nommer.read_n((length - 6) as usize)?;
Ok(Self { Ok(Self { indicator, bitmap })
indicator,
bitmap
})
} }
} }
pub struct DataSection { pub struct DataSection {
pub data: Vec<u8> pub data: Vec<u8>,
} }
impl DataSection { impl DataSection {
fn parse<R: Read>(length: u32, nommer: &mut NomReader<R>) -> Result<Self, GribError> { fn parse<R: Read>(length: u32, nommer: &mut NomReader<R>) -> Result<Self, GribError> {
let data = nommer.read_n((length - 5) as usize)?; let data = nommer.read_n((length - 5) as usize)?;
Ok(Self { Ok(Self { data })
data
})
} }
} }
impl Debug for DataSection { impl Debug for DataSection {
@ -454,7 +468,7 @@ pub struct HorizontalLayerInstantaneousProductDef {
pub scaled_value_first_fixed_surface: u32, pub scaled_value_first_fixed_surface: u32,
pub second_fixed_surface_type: u8, pub second_fixed_surface_type: u8,
pub scale_factor_second_fixed_surface: u8, pub scale_factor_second_fixed_surface: u8,
pub scaled_value_second_fixed_surface: u32 pub scaled_value_second_fixed_surface: u32,
} }
impl HorizontalLayerInstantaneousProductDef { impl HorizontalLayerInstantaneousProductDef {
fn parse<R: Read>(_length: u32, nommer: &mut NomReader<R>) -> Result<Self, GribError> { fn parse<R: Read>(_length: u32, nommer: &mut NomReader<R>) -> Result<Self, GribError> {
@ -495,12 +509,12 @@ impl HorizontalLayerInstantaneousProductDef {
#[derive(Debug)] #[derive(Debug)]
pub enum GridDefinition { pub enum GridDefinition {
LatitudeLongitude(LatLongGrid) LatitudeLongitude(LatLongGrid),
} }
impl GridDefinition { impl GridDefinition {
fn image_coordinates(&self, at: LatLong) -> Option<(u64, u64)> { fn image_coordinates(&self, at: LatLong) -> Option<(u64, u64)> {
match self { match self {
GridDefinition::LatitudeLongitude(grid) => grid.image_coordinates(at) GridDefinition::LatitudeLongitude(grid) => grid.image_coordinates(at),
} }
} }
} }
@ -532,37 +546,41 @@ pub struct LatLongGrid {
#[derive(Debug)] #[derive(Debug)]
pub enum LatLongVectorRelativity { pub enum LatLongVectorRelativity {
EasterlyAndNortherly, EasterlyAndNortherly,
IncreasingXY IncreasingXY,
} }
impl LatLongGrid { impl LatLongGrid {
fn parse<R: Read>(length: u32, nommer: &mut NomReader<R>) -> Result<Self, GribError> { fn parse<R: Read>(length: u32, nommer: &mut NomReader<R>) -> Result<Self, GribError> {
if length != 72 { if length != 72 {
return Err(GribError::ListOfNumbersNotSupported); return Err(GribError::ListOfNumbersNotSupported);
} }
let shape_of_the_earth= nommer.read_u8()?; let shape_of_the_earth = nommer.read_u8()?;
let scale_factor_of_radius_of_spherical_earth= nommer.read_u8()?; let scale_factor_of_radius_of_spherical_earth = nommer.read_u8()?;
let scale_value_of_radius_of_spherical_earth= nommer.read_u32()?; let scale_value_of_radius_of_spherical_earth = nommer.read_u32()?;
let scale_factor_of_major_axis_of_oblate_spheroid_earth= nommer.read_u8()?; let scale_factor_of_major_axis_of_oblate_spheroid_earth = nommer.read_u8()?;
let scaled_value_of_major_axis_of_oblate_spheroid_earth= nommer.read_u32()?; let scaled_value_of_major_axis_of_oblate_spheroid_earth = nommer.read_u32()?;
let scale_factor_of_minor_axis_of_oblate_spheroid_earth= nommer.read_u8()?; let scale_factor_of_minor_axis_of_oblate_spheroid_earth = nommer.read_u8()?;
let scaled_value_of_minor_axis_of_oblate_spheroid_earth= nommer.read_u32()?; let scaled_value_of_minor_axis_of_oblate_spheroid_earth = nommer.read_u32()?;
let ni= nommer.read_u32()?; // number of points along a parallel let ni = nommer.read_u32()?; // number of points along a parallel
let nj= nommer.read_u32()?; // number of points along a meridian let nj = nommer.read_u32()?; // number of points along a meridian
let basic_angle_of_the_initial_production_domain= nommer.read_u32()?; let basic_angle_of_the_initial_production_domain = nommer.read_u32()?;
let subdivisions_of_basic_angle= nommer.read_u32()?; let subdivisions_of_basic_angle = nommer.read_u32()?;
let la1= nommer.read_u32()? as f64 * 10.0_f64.powi(-6); let la1 = nommer.read_u32()? as f64 * 10.0_f64.powi(-6);
let lo1= convert_longitude(nommer.read_u32()? as f64 * 10.0_f64.powi(-6)); let lo1 = convert_longitude(nommer.read_u32()? as f64 * 10.0_f64.powi(-6));
let resolution_and_component_flags= nommer.read_u8()?; let resolution_and_component_flags = nommer.read_u8()?;
let i_direction_increments_given = resolution_and_component_flags & (1 << 5) != 0; let i_direction_increments_given = resolution_and_component_flags & (1 << 5) != 0;
let j_direction_increments_given = resolution_and_component_flags & (1 << 4) != 0; let j_direction_increments_given = resolution_and_component_flags & (1 << 4) != 0;
let vector_relativity = if resolution_and_component_flags & (1 << 3) != 0 { IncreasingXY } else { EasterlyAndNortherly }; let vector_relativity = if resolution_and_component_flags & (1 << 3) != 0 {
IncreasingXY
} else {
EasterlyAndNortherly
};
let la2= nommer.read_u32()? as f64 * 10.0_f64.powi(-6); let la2 = nommer.read_u32()? as f64 * 10.0_f64.powi(-6);
let lo2= convert_longitude(nommer.read_u32()? as f64 * 10.0_f64.powi(-6)); let lo2 = convert_longitude(nommer.read_u32()? as f64 * 10.0_f64.powi(-6));
let di= nommer.read_u32()? as f64 * 10.0_f64.powi(-6); let di = nommer.read_u32()? as f64 * 10.0_f64.powi(-6);
let dj= nommer.read_u32()? as f64 * 10.0_f64.powi(-6); let dj = nommer.read_u32()? as f64 * 10.0_f64.powi(-6);
let scanning_mode_flags= nommer.read_u8()?; let scanning_mode_flags = nommer.read_u8()?;
Ok(Self { Ok(Self {
shape_of_the_earth, shape_of_the_earth,
@ -590,10 +608,14 @@ impl LatLongGrid {
} }
fn image_coordinates(&self, at: LatLong) -> Option<(u64, u64)> { fn image_coordinates(&self, at: LatLong) -> Option<(u64, u64)> {
if at.lat > self.la1 || at.lat < self.la2 { return None; } if at.lat > self.la1 || at.lat < self.la2 {
if at.long < self.lo1 || at.long > self.lo2 { return None; } return None;
}
if at.long < self.lo1 || at.long > self.lo2 {
return None;
}
let lat = ((at.lat - self.la1) / self.di ).round() * self.di + self.la1; let lat = ((at.lat - self.la1) / self.di).round() * self.di + self.la1;
let long = ((at.long - self.lo1) / self.dj).round() * self.dj + self.lo1; let long = ((at.long - self.lo1) / self.dj).round() * self.dj + self.lo1;
if (lat - at.lat).abs() > self.di || (long - at.long).abs() > self.dj { if (lat - at.lat).abs() > self.di || (long - at.long).abs() > self.dj {
@ -610,9 +632,6 @@ impl LatLongGrid {
} }
} }
fn convert_longitude(longitude: f64) -> f64 { fn convert_longitude(longitude: f64) -> f64 {
if longitude > 180.0 { if longitude > 180.0 {
longitude - 360.0 longitude - 360.0

View file

@ -2,13 +2,11 @@ use std::io::Read;
#[derive(Debug)] #[derive(Debug)]
pub struct NomReader<R: Read> { pub struct NomReader<R: Read> {
inner: R inner: R,
} }
impl<R: Read> NomReader<R> { impl<R: Read> NomReader<R> {
pub fn new(inner: R) -> NomReader<R> { pub fn new(inner: R) -> NomReader<R> {
Self { Self { inner }
inner
}
} }
pub fn read_u8(&mut self) -> Result<u8, std::io::Error> { pub fn read_u8(&mut self) -> Result<u8, std::io::Error> {

View file

@ -1,5 +1,5 @@
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub struct LatLong { pub struct LatLong {
pub lat: f64, pub lat: f64,
pub long: f64 pub long: f64,
} }

View file

@ -1,5 +1,5 @@
pub mod parser;
pub mod default_palettes; pub mod default_palettes;
pub mod parser;
pub use ordered_float::OrderedFloat; pub use ordered_float::OrderedFloat;
@ -8,7 +8,7 @@ pub struct Color {
pub red: u8, pub red: u8,
pub green: u8, pub green: u8,
pub blue: u8, pub blue: u8,
pub alpha: u8 pub alpha: u8,
} }
pub type Palette = Vec<((OrderedFloat<f64>, OrderedFloat<f64>), (Color, Color))>; pub type Palette = Vec<((OrderedFloat<f64>, OrderedFloat<f64>), (Color, Color))>;
@ -16,10 +16,20 @@ pub type Palette = Vec<((OrderedFloat<f64>, OrderedFloat<f64>), (Color, Color))>
#[macro_export] #[macro_export]
macro_rules! c { macro_rules! c {
($r:expr,$g:expr,$b:expr) => { ($r:expr,$g:expr,$b:expr) => {
crate::Color { red: $r, green: $g, blue: $b, alpha: 255 } crate::Color {
red: $r,
green: $g,
blue: $b,
alpha: 255,
}
}; };
($r:expr,$g:expr,$b:expr,$a:expr) => { ($r:expr,$g:expr,$b:expr,$a:expr) => {
crate::Color { red: $r, green: $g, blue: $b, alpha: $a } crate::Color {
red: $r,
green: $g,
blue: $b,
alpha: $a,
}
}; };
} }
@ -36,9 +46,8 @@ pub trait ColorPalette {
impl ColorPalette for Palette { impl ColorPalette for Palette {
fn colorize(&self, at: f64) -> Color { fn colorize(&self, at: f64) -> Color {
for (raw_range, color_range) in self { for (raw_range, color_range) in self {
let range = *raw_range.0 .. *raw_range.1; let range = *raw_range.0..*raw_range.1;
if range.contains(&at) { if range.contains(&at) {
let mapped_end = *raw_range.1 - *raw_range.0; let mapped_end = *raw_range.1 - *raw_range.0;
let mapped_point = at - *raw_range.0; let mapped_point = at - *raw_range.0;
let t = mapped_point / mapped_end; let t = mapped_point / mapped_end;
@ -53,7 +62,7 @@ impl ColorPalette for Palette {
red: 0, red: 0,
green: 0, green: 0,
blue: 0, blue: 0,
alpha: 0 alpha: 0,
} }
} }
} }
@ -66,6 +75,6 @@ fn lerp_color(c0: Color, c1: Color, t: f64) -> Color {
red: lerp(c0.red as f64, c1.red as f64, t) as u8, red: lerp(c0.red as f64, c1.red as f64, t) as u8,
green: lerp(c0.green as f64, c1.green as f64, t) as u8, green: lerp(c0.green as f64, c1.green as f64, t) as u8,
blue: lerp(c0.blue as f64, c1.blue as f64, t) as u8, blue: lerp(c0.blue as f64, c1.blue as f64, t) as u8,
alpha: lerp(c0.alpha as f64, c1.alpha as f64, t) as u8 alpha: lerp(c0.alpha as f64, c1.alpha as f64, t) as u8,
} }
} }

View file

@ -1,7 +1,7 @@
use crate::{c, r, Palette};
use std::num::{ParseFloatError, ParseIntError}; use std::num::{ParseFloatError, ParseIntError};
use std::str::FromStr; use std::str::FromStr;
use thiserror::Error; use thiserror::Error;
use crate::{c, Palette, r};
#[derive(Error, Debug, Clone)] #[derive(Error, Debug, Clone)]
pub enum PaletteParseError { pub enum PaletteParseError {
@ -16,7 +16,7 @@ pub enum PaletteParseError {
#[error("invalid int")] #[error("invalid int")]
InvalidInt(#[from] ParseIntError), InvalidInt(#[from] ParseIntError),
#[error("palette is empty")] #[error("palette is empty")]
PaletteEmpty PaletteEmpty,
} }
pub fn parse(pal_str: &str) -> Result<Palette, PaletteParseError> { pub fn parse(pal_str: &str) -> Result<Palette, PaletteParseError> {
@ -52,7 +52,7 @@ pub fn parse(pal_str: &str) -> Result<Palette, PaletteParseError> {
} else { } else {
return Err(PaletteParseError::IncorrectColorCommandLength(tokens.len())); return Err(PaletteParseError::IncorrectColorCommandLength(tokens.len()));
} }
}, }
"color4:" => { "color4:" => {
if tokens.len() == 6 { if tokens.len() == 6 {
// color4: val r g b a (5 tkns + command = 6 total) // color4: val r g b a (5 tkns + command = 6 total)
@ -77,54 +77,54 @@ pub fn parse(pal_str: &str) -> Result<Palette, PaletteParseError> {
} else { } else {
return Err(PaletteParseError::IncorrectColorCommandLength(tokens.len())); return Err(PaletteParseError::IncorrectColorCommandLength(tokens.len()));
} }
}, }
"product:" => {}, // valid but ignored "product:" => {} // valid but ignored
"units:" => {}, // valid but ignored "units:" => {} // valid but ignored
"step:" => {}, // valid but ignored "step:" => {} // valid but ignored
unknown => return Err(PaletteParseError::UnknownCommand(unknown.to_string())) unknown => return Err(PaletteParseError::UnknownCommand(unknown.to_string())),
} }
} }
if parsed_data.is_empty() { if parsed_data.is_empty() {
return Err(PaletteParseError::PaletteEmpty) return Err(PaletteParseError::PaletteEmpty);
} }
let mut intermediate_with_infinities_added: Vec<(f64, u8, u8, u8, u8, u8, u8, u8, u8)> = vec![]; let mut intermediate_with_infinities_added: Vec<(f64, u8, u8, u8, u8, u8, u8, u8, u8)> = vec![];
intermediate_with_infinities_added.push( intermediate_with_infinities_added.push((
( f64::NEG_INFINITY,
f64::NEG_INFINITY, parsed_data[0].1,
parsed_data[0].1, parsed_data[0].2,
parsed_data[0].2, parsed_data[0].3,
parsed_data[0].3, parsed_data[0].4,
parsed_data[0].4, parsed_data[0].1,
parsed_data[0].1, parsed_data[0].2,
parsed_data[0].2, parsed_data[0].3,
parsed_data[0].3, parsed_data[0].4,
parsed_data[0].4, ));
)
);
intermediate_with_infinities_added.append(&mut parsed_data); intermediate_with_infinities_added.append(&mut parsed_data);
intermediate_with_infinities_added.push( intermediate_with_infinities_added.push((
( f64::INFINITY,
f64::INFINITY, intermediate_with_infinities_added[intermediate_with_infinities_added.len() - 1].1,
intermediate_with_infinities_added[intermediate_with_infinities_added.len()-1].1, intermediate_with_infinities_added[intermediate_with_infinities_added.len() - 1].2,
intermediate_with_infinities_added[intermediate_with_infinities_added.len()-1].2, intermediate_with_infinities_added[intermediate_with_infinities_added.len() - 1].3,
intermediate_with_infinities_added[intermediate_with_infinities_added.len()-1].3, intermediate_with_infinities_added[intermediate_with_infinities_added.len() - 1].4,
intermediate_with_infinities_added[intermediate_with_infinities_added.len()-1].4, intermediate_with_infinities_added[intermediate_with_infinities_added.len() - 1].1,
intermediate_with_infinities_added[intermediate_with_infinities_added.len()-1].1, intermediate_with_infinities_added[intermediate_with_infinities_added.len() - 1].2,
intermediate_with_infinities_added[intermediate_with_infinities_added.len()-1].2, intermediate_with_infinities_added[intermediate_with_infinities_added.len() - 1].3,
intermediate_with_infinities_added[intermediate_with_infinities_added.len()-1].3, intermediate_with_infinities_added[intermediate_with_infinities_added.len() - 1].4,
intermediate_with_infinities_added[intermediate_with_infinities_added.len()-1].4, ));
)
);
let mut output = vec![]; let mut output = vec![];
for range in intermediate_with_infinities_added.windows(2) { for range in intermediate_with_infinities_added.windows(2) {
output.push( output.push((
(r!(range[0].0, range[1].0), (c!(range[0].1, range[0].2, range[0].3, range[0].4), c!(range[0].5, range[0].6, range[0].7, range[0].8))) r!(range[0].0, range[1].0),
) (
c!(range[0].1, range[0].2, range[0].3, range[0].4),
c!(range[0].5, range[0].6, range[0].7, range[0].8),
),
))
} }
Ok(output) Ok(output)
@ -138,7 +138,8 @@ mod tests {
#[test] #[test]
fn almanydesigns_radaromega_nws_default() { fn almanydesigns_radaromega_nws_default() {
assert_eq!( assert_eq!(
parse(r#"color: -30 165 165 165 8 230 230 parse(
r#"color: -30 165 165 165 8 230 230
color: 10 0 165 255 0 8 197 color: 10 0 165 255 0 8 197
color: 20 16 255 8 10 126 3 color: 20 16 255 8 10 126 3
color: 35 251 238 0 210 112 2 color: 35 251 238 0 210 112 2
@ -146,9 +147,14 @@ color: 50 255 0 0 171 0 1
color: 65 247 1 249 136 63 174 color: 65 247 1 249 136 63 174
color: 75 255 255 255 184 184 184 color: 75 255 255 255 184 184 184
color: 85 184 184 184 color: 85 184 184 184
color: 95 184 184 184"#).unwrap(), color: 95 184 184 184"#
)
.unwrap(),
vec![ vec![
(r!(f64::NEG_INFINITY, -30.0), (c!(165, 165, 165), c!(165, 165, 165))), (
r!(f64::NEG_INFINITY, -30.0),
(c!(165, 165, 165), c!(165, 165, 165))
),
(r!(-30.0, 10.0), (c!(165, 165, 165), c!(8, 230, 230))), (r!(-30.0, 10.0), (c!(165, 165, 165), c!(8, 230, 230))),
(r!(10.0, 20.0), (c!(0, 165, 255), c!(0, 8, 197))), (r!(10.0, 20.0), (c!(0, 165, 255), c!(0, 8, 197))),
(r!(20.0, 35.0), (c!(16, 255, 8), c!(10, 126, 3))), (r!(20.0, 35.0), (c!(16, 255, 8), c!(10, 126, 3))),
@ -157,7 +163,10 @@ color: 95 184 184 184"#).unwrap(),
(r!(65.0, 75.0), (c!(247, 1, 249), c!(136, 63, 174))), (r!(65.0, 75.0), (c!(247, 1, 249), c!(136, 63, 174))),
(r!(75.0, 85.0), (c!(255, 255, 255), c!(184, 184, 184))), (r!(75.0, 85.0), (c!(255, 255, 255), c!(184, 184, 184))),
(r!(85.0, 95.0), (c!(184, 184, 184), c!(184, 184, 184))), (r!(85.0, 95.0), (c!(184, 184, 184), c!(184, 184, 184))),
(r!(95.0, f64::INFINITY), (c!(184, 184, 184), c!(184, 184, 184))) (
r!(95.0, f64::INFINITY),
(c!(184, 184, 184), c!(184, 184, 184))
)
] ]
) )
} }

View file

@ -1,14 +1,14 @@
use std::collections::HashMap;
use serde::Deserialize; use serde::Deserialize;
use std::collections::HashMap;
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct Config { pub struct Config {
pub sources: Sources pub sources: Sources,
} }
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct Sources { pub struct Sources {
pub grib2: HashMap<String, Grib2Source> pub grib2: HashMap<String, Grib2Source>,
} }
#[derive(Deserialize)] #[derive(Deserialize)]
@ -19,5 +19,5 @@ pub struct Grib2Source {
pub palette: String, pub palette: String,
pub missing: Option<f64>, pub missing: Option<f64>,
pub range_folded: Option<f64>, pub range_folded: Option<f64>,
pub no_coverage: Option<f64> pub no_coverage: Option<f64>,
} }

View file

@ -1,24 +1,24 @@
pub(crate) mod sources; pub(crate) mod sources;
mod pixmap;
mod config; mod config;
mod pixmap;
use crate::config::Config;
use actix_web::web::Data;
use actix_web::{App, HttpServer};
use std::collections::{BTreeMap, HashMap}; use std::collections::{BTreeMap, HashMap};
use std::env::args; use std::env::args;
use std::fmt::Debug; use std::fmt::Debug;
use std::fs; use std::fs;
use std::sync::Arc; use std::sync::Arc;
use tokio::sync::RwLock;
use std::time::SystemTime; use std::time::SystemTime;
use actix_web::{App, HttpServer}; use tokio::sync::RwLock;
use actix_web::web::Data;
use wxbox_grib2::GribMessage; use wxbox_grib2::GribMessage;
use crate::config::Config;
pub struct AppState { pub struct AppState {
grib2_cache: RwLock<HashMap<String, Arc<RwLock<GribMessage>>>>, grib2_cache: RwLock<HashMap<String, Arc<RwLock<GribMessage>>>>,
grib2_cache_timestamps: RwLock<HashMap<String, SystemTime>>, grib2_cache_timestamps: RwLock<HashMap<String, SystemTime>>,
config: Config config: Config,
} }
#[actix_web::main] #[actix_web::main]
@ -32,16 +32,14 @@ async fn main() -> std::io::Result<()> {
let data = Data::new(AppState { let data = Data::new(AppState {
grib2_cache: RwLock::new(HashMap::new()), grib2_cache: RwLock::new(HashMap::new()),
grib2_cache_timestamps: RwLock::new(HashMap::new()), grib2_cache_timestamps: RwLock::new(HashMap::new()),
config config,
}); });
HttpServer::new(move || { HttpServer::new(move || {
App::new() App::new()
.service(sources::grib2::source) .service(sources::grib2::source)
.app_data(data.clone()) .app_data(data.clone())
}) })
.bind(("::", 8080))? .bind(("::", 8080))?
.run() .run()
.await .await
} }

View file

@ -1,15 +1,18 @@
use wxbox_pal::Color; use wxbox_pal::Color;
pub struct Pixmap { pub struct Pixmap {
data: [Color; 256 * 256] data: [Color; 256 * 256],
} }
impl Pixmap { impl Pixmap {
#[inline] #[inline]
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
data: [Color { red: 0, green: 0, blue: 0, alpha: 0 }; 256 * 256] data: [Color {
red: 0,
green: 0,
blue: 0,
alpha: 0,
}; 256 * 256],
} }
} }
#[inline] #[inline]

View file

@ -1,33 +1,40 @@
use crate::config::Grib2Source;
use crate::pixmap::Pixmap;
use crate::AppState;
use actix_web::error::UrlencodedError::ContentType;
use actix_web::http::StatusCode;
use actix_web::web::{Data, Query};
use actix_web::HttpResponse;
use flate2::read::GzDecoder;
use image::{ImageFormat, ImageReader};
use png::{BitDepth, ColorType, Encoder};
use reqwest::ClientBuilder;
use serde::Deserialize;
use std::collections::{BTreeMap, HashMap}; use std::collections::{BTreeMap, HashMap};
use std::f64::consts::PI; use std::f64::consts::PI;
use std::io::{BufWriter, Cursor, Read}; use std::io::{BufWriter, Cursor, Read};
use std::ops::{Add, Div, Mul, Sub}; use std::ops::{Add, Div, Mul, Sub};
use std::sync::Arc; use std::sync::Arc;
use std::time::SystemTime; use std::time::SystemTime;
use actix_web::error::UrlencodedError::ContentType;
use actix_web::http::StatusCode;
use actix_web::HttpResponse;
use actix_web::web::{Data, Query};
use flate2::read::GzDecoder;
use png::{BitDepth, ColorType, Encoder};
use serde::Deserialize;
use tokio::sync::RwLock; use tokio::sync::RwLock;
use wxbox_common::TileRequestOptions;
use image::{ImageFormat, ImageReader};
use reqwest::ClientBuilder;
use wxbox_grib2::GribMessage;
use wxbox_grib2::wgs84::LatLong;
use wxbox_pal::{Color, ColorPalette, Palette};
use crate::AppState;
use tracing::{debug, info}; use tracing::{debug, info};
use crate::config::Grib2Source; use wxbox_common::TileRequestOptions;
use crate::pixmap::Pixmap; use wxbox_grib2::wgs84::LatLong;
use wxbox_grib2::GribMessage;
use wxbox_pal::{Color, ColorPalette, Palette};
pub async fn needs_reload(lct: &RwLock<HashMap<String, SystemTime>>, lutkey: &String, valid_for: u64) -> bool { pub async fn needs_reload(
lct: &RwLock<HashMap<String, SystemTime>>,
lutkey: &String,
valid_for: u64,
) -> bool {
let lct_reader = lct.read().await; let lct_reader = lct.read().await;
if let Some(t) = lct_reader.get(lutkey) { if let Some(t) = lct_reader.get(lutkey) {
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 > valid_for { if dur > valid_for {
return true; return true;
} }
@ -59,15 +66,24 @@ pub async fn load(url: &str, is_gzipped: bool) -> Vec<u8> {
out out
} }
pub async fn reload_if_required(
pub async fn reload_if_required(from: &str, needs_gzip: bool, valid_for: u64, lut_key: &String, lct_cache: &RwLock<HashMap<String, SystemTime>>, lut_cache: &RwLock<HashMap<String, Arc<RwLock<GribMessage>>>>) { from: &str,
needs_gzip: bool,
valid_for: u64,
lut_key: &String,
lct_cache: &RwLock<HashMap<String, SystemTime>>,
lut_cache: &RwLock<HashMap<String, Arc<RwLock<GribMessage>>>>,
) {
if needs_reload(&lct_cache, lut_key, valid_for).await { if needs_reload(&lct_cache, lut_key, valid_for).await {
let mut lct_writer = lct_cache.write().await; let mut lct_writer = lct_cache.write().await;
let message = load(from, needs_gzip).await; let message = load(from, needs_gzip).await;
let grib = GribMessage::new(Cursor::new(message)).unwrap(); let grib = GribMessage::new(Cursor::new(message)).unwrap();
lut_cache.write().await.insert(lut_key.clone(), Arc::new(RwLock::new(grib))); lut_cache
.write()
.await
.insert(lut_key.clone(), Arc::new(RwLock::new(grib)));
lct_writer.insert(lut_key.clone(), SystemTime::now()); lct_writer.insert(lut_key.clone(), SystemTime::now());
} }
} }
@ -75,7 +91,18 @@ pub async fn reload_if_required(from: &str, needs_gzip: bool, valid_for: u64, lu
const TWO_PI: f64 = PI * 2.0; const TWO_PI: f64 = PI * 2.0;
const HALF_PI: f64 = PI / 2.0; const HALF_PI: f64 = PI / 2.0;
pub async fn render(xtile: f64, ytile: f64, z: i32, tilesize: usize, pal: Palette, map: &Arc<RwLock<GribMessage>>, missing: Option<f64>, range_folded: Option<f64>, no_coverage: Option<f64>, options: &TileRequestOptions) -> Pixmap { pub async fn render(
xtile: f64,
ytile: f64,
z: i32,
tilesize: usize,
pal: Palette,
map: &Arc<RwLock<GribMessage>>,
missing: Option<f64>,
range_folded: Option<f64>,
no_coverage: Option<f64>,
options: &TileRequestOptions,
) -> Pixmap {
let mut image: Pixmap = Pixmap::new(); let mut image: Pixmap = Pixmap::new();
let denominator = 2.0_f64.powi(z) * tilesize as f64; let denominator = 2.0_f64.powi(z) * tilesize as f64;
@ -92,22 +119,41 @@ pub async fn render(xtile: f64, ytile: f64, z: i32, tilesize: usize, pal: Palett
let lon = (TWO_PI * tx - PI).to_degrees(); let lon = (TWO_PI * tx - PI).to_degrees();
let lat = ((PI - TWO_PI * ty).exp().atan() * 2.0_f64 - HALF_PI).to_degrees(); let lat = ((PI - TWO_PI * ty).exp().atan() * 2.0_f64 - HALF_PI).to_degrees();
let nearest = message.value_for(LatLong { let nearest = message
lat, .value_for(LatLong { lat, long: lon })
long: lon .map(|u| u as f64);
}).map(|u| u as f64);
let color = match nearest { let color = match nearest {
Some(c) if Some(c) == no_coverage => Color { red: 0, green: 0, blue: 0, alpha: 30 }, Some(c) if Some(c) == no_coverage => Color {
Some(c) if Some(c) == missing => Color { red: 0, green: 0, blue: 0, alpha: 0 }, red: 0,
green: 0,
blue: 0,
alpha: 30,
},
Some(c) if Some(c) == missing => Color {
red: 0,
green: 0,
blue: 0,
alpha: 0,
},
Some(c) if Some(c) == range_folded => { Some(c) if Some(c) == range_folded => {
if options.show_range_folded { if options.show_range_folded {
let color_raw = options.range_folded_color.to_be_bytes(); let color_raw = options.range_folded_color.to_be_bytes();
Color { red: color_raw[0], green: color_raw[1], blue: color_raw[2], alpha: color_raw[3] } Color {
red: color_raw[0],
green: color_raw[1],
blue: color_raw[2],
alpha: color_raw[3],
}
} else { } else {
Color { red: 0, green: 0, blue: 0, alpha: 0 } Color {
red: 0,
green: 0,
blue: 0,
alpha: 0,
}
} }
}, }
Some(value_at_pos) => { Some(value_at_pos) => {
let mut c = pal.colorize(value_at_pos); let mut c = pal.colorize(value_at_pos);
if c.red == 0 && c.green == 0 && c.blue == 0 { if c.red == 0 && c.green == 0 && c.blue == 0 {
@ -116,8 +162,13 @@ pub async fn render(xtile: f64, ytile: f64, z: i32, tilesize: usize, pal: Palett
c.alpha = coloru8(options.data_transparency); c.alpha = coloru8(options.data_transparency);
} }
c c
}
None => Color {
red: 0,
green: 0,
blue: 0,
alpha: 30,
}, },
None => Color { red: 0, green: 0, blue: 0, alpha: 30 }
}; };
image.set(y, x, color); image.set(y, x, color);
@ -130,7 +181,7 @@ pub async fn render(xtile: f64, ytile: f64, z: i32, tilesize: usize, pal: Palett
#[derive(Deserialize)] #[derive(Deserialize)]
struct QueryReq { struct QueryReq {
settings: String settings: String,
} }
fn colorf64(i: u8) -> f64 { fn colorf64(i: u8) -> f64 {
@ -145,7 +196,7 @@ pub struct ColorF64 {
pub red: f64, pub red: f64,
pub green: f64, pub green: f64,
pub blue: f64, pub blue: f64,
pub alpha: f64 pub alpha: f64,
} }
impl From<Color> for ColorF64 { impl From<Color> for ColorF64 {
fn from(value: Color) -> Self { fn from(value: Color) -> Self {
@ -153,7 +204,7 @@ impl From<Color> for ColorF64 {
red: colorf64(value.red), red: colorf64(value.red),
green: colorf64(value.green), green: colorf64(value.green),
blue: colorf64(value.blue), blue: colorf64(value.blue),
alpha: colorf64(value.alpha) alpha: colorf64(value.alpha),
} }
} }
} }
@ -163,7 +214,7 @@ impl From<ColorF64> for Color {
red: coloru8(value.red), red: coloru8(value.red),
green: coloru8(value.green), green: coloru8(value.green),
blue: coloru8(value.blue), blue: coloru8(value.blue),
alpha: coloru8(value.alpha) alpha: coloru8(value.alpha),
} }
} }
} }
@ -175,7 +226,7 @@ impl Add for ColorF64 {
red: self.red + rhs.red, red: self.red + rhs.red,
green: self.green + rhs.green, green: self.green + rhs.green,
blue: self.blue + rhs.blue, blue: self.blue + rhs.blue,
alpha: self.alpha + rhs.alpha alpha: self.alpha + rhs.alpha,
} }
} }
} }
@ -187,7 +238,7 @@ impl Sub for ColorF64 {
red: self.red - rhs.red, red: self.red - rhs.red,
green: self.green - rhs.green, green: self.green - rhs.green,
blue: self.blue - rhs.blue, blue: self.blue - rhs.blue,
alpha: self.alpha - rhs.alpha alpha: self.alpha - rhs.alpha,
} }
} }
} }
@ -199,7 +250,7 @@ impl Sub<ColorF64> for f64 {
red: rhs.red - self, red: rhs.red - self,
green: rhs.green - self, green: rhs.green - self,
blue: rhs.blue - self, blue: rhs.blue - self,
alpha: rhs.alpha - self alpha: rhs.alpha - self,
} }
} }
} }
@ -211,7 +262,7 @@ impl Mul<f64> for ColorF64 {
red: self.red * rhs, red: self.red * rhs,
blue: self.blue * rhs, blue: self.blue * rhs,
green: self.green * rhs, green: self.green * rhs,
alpha: self.alpha * rhs alpha: self.alpha * rhs,
} }
} }
} }
@ -223,7 +274,7 @@ impl Mul for ColorF64 {
red: self.red * rhs.red, red: self.red * rhs.red,
green: self.green * rhs.green, green: self.green * rhs.green,
blue: self.blue * rhs.blue, blue: self.blue * rhs.blue,
alpha: self.alpha * rhs.alpha alpha: self.alpha * rhs.alpha,
} }
} }
} }
@ -235,7 +286,7 @@ impl Div for ColorF64 {
red: self.red / rhs.red, red: self.red / rhs.red,
green: self.green / rhs.green, green: self.green / rhs.green,
blue: self.blue / rhs.blue, blue: self.blue / rhs.blue,
alpha: self.alpha / rhs.alpha alpha: self.alpha / rhs.alpha,
} }
} }
} }
@ -247,12 +298,11 @@ impl Div<f64> for ColorF64 {
red: self.red / rhs, red: self.red / rhs,
green: self.green / rhs, green: self.green / rhs,
blue: self.blue / rhs, blue: self.blue / rhs,
alpha: self.alpha / rhs alpha: self.alpha / rhs,
} }
} }
} }
pub fn merge(base: Pixmap, data: Pixmap) -> Pixmap { pub fn merge(base: Pixmap, data: Pixmap) -> Pixmap {
let mut new = Pixmap::new(); let mut new = Pixmap::new();
@ -264,7 +314,8 @@ pub fn merge(base: Pixmap, data: Pixmap) -> Pixmap {
if c_s.red == 0.0 && c_s.green == 0.0 && c_s.blue == 0.0 && c_s.alpha == 0.0 { if c_s.red == 0.0 && c_s.green == 0.0 && c_s.blue == 0.0 && c_s.alpha == 0.0 {
new.set(x, y, c_b.into()); new.set(x, y, c_b.into());
} else { } else {
let mut co = (c_s * c_s.alpha + c_b * c_b.alpha * (1.0 - c_s.alpha)) / (c_s.alpha + c_b.alpha * (1.0 - c_s.alpha)); let mut co = (c_s * c_s.alpha + c_b * c_b.alpha * (1.0 - c_s.alpha))
/ (c_s.alpha + c_b.alpha * (1.0 - c_s.alpha));
co.alpha = 1.0; co.alpha = 1.0;
new.set(x, y, co.into()); new.set(x, y, co.into());
@ -276,7 +327,11 @@ pub fn merge(base: Pixmap, data: Pixmap) -> Pixmap {
} }
#[actix_web::get("/{z}/{x}/{y}.png")] #[actix_web::get("/{z}/{x}/{y}.png")]
pub async fn source(path: actix_web::web::Path<(i32, u32, u32)>, req: Query<QueryReq>, data: Data<AppState>) -> HttpResponse { pub async fn source(
path: actix_web::web::Path<(i32, u32, u32)>,
req: Query<QueryReq>,
data: Data<AppState>,
) -> HttpResponse {
let settings: TileRequestOptions = serde_json::from_str(&req.settings).unwrap(); let settings: TileRequestOptions = serde_json::from_str(&req.settings).unwrap();
// todo: load the base layer from external source // todo: load the base layer from external source
@ -285,31 +340,44 @@ pub async fn source(path: actix_web::web::Path<(i32, u32, u32)>, req: Query<Quer
let base_layer: Pixmap = if settings.baselayer == "osm" { let base_layer: Pixmap = if settings.baselayer == "osm" {
let client = ClientBuilder::new() let client = ClientBuilder::new()
.user_agent(format!("wxbox-tiler/{}", env!("CARGO_PKG_VERSION"))) .user_agent(format!("wxbox-tiler/{}", env!("CARGO_PKG_VERSION")))
.build().unwrap(); .build()
let body = client.get(format!("https://tile.openstreetmap.org/{}/{}/{}.png", path.0, path.1, path.2)) .unwrap();
let body = client
.get(format!(
"https://tile.openstreetmap.org/{}/{}/{}.png",
path.0, path.1, path.2
))
.send() .send()
.await.unwrap().bytes().await.unwrap(); .await
.unwrap()
.bytes()
.await
.unwrap();
let mut img = ImageReader::new(Cursor::new(body.to_vec())); let mut img = ImageReader::new(Cursor::new(body.to_vec()));
img.set_format(ImageFormat::Png); img.set_format(ImageFormat::Png);
let img = img.decode().unwrap(); let img = img.decode().unwrap();
let rgb = img.into_rgba8(); let rgb = img.into_rgba8();
// copy it into a pixmap // copy it into a pixmap
let mut map = Pixmap::new(); let mut map = Pixmap::new();
for x in 0..256_usize { for x in 0..256_usize {
for y in 0..256_usize { for y in 0..256_usize {
let pix = rgb.get_pixel(y as u32, x as u32); let pix = rgb.get_pixel(y as u32, x as u32);
map.set(x, y, Color { map.set(
red: pix[0], x,
green: pix[1], y,
blue: pix[2], Color {
alpha: pix[3] red: pix[0],
}); green: pix[1],
blue: pix[2],
alpha: pix[3],
},
);
} }
} }
map map
} else { } else {
debug!("not found baselayer"); debug!("not found baselayer");
return HttpResponse::new(StatusCode::NOT_FOUND) return HttpResponse::new(StatusCode::NOT_FOUND);
}; };
// data layer // data layer
@ -317,29 +385,47 @@ pub async fn source(path: actix_web::web::Path<(i32, u32, u32)>, req: Query<Quer
// - grib2/ // - grib2/
let data_layer: Pixmap = if settings.data.starts_with("grib2/") { let data_layer: Pixmap = if settings.data.starts_with("grib2/") {
if let Some(known_source) = data.config.sources.grib2.get(settings.data.strip_prefix("grib2/").unwrap()) { if let Some(known_source) = data
.config
.sources
.grib2
.get(settings.data.strip_prefix("grib2/").unwrap())
{
reload_if_required( reload_if_required(
&known_source.from, &known_source.from,
known_source.needs_gzip, known_source.needs_gzip,
known_source.valid_for.into(), known_source.valid_for.into(),
&settings.data, &settings.data,
&data.grib2_cache_timestamps, &data.grib2_cache_timestamps,
&data.grib2_cache &data.grib2_cache,
).await; )
.await;
let lct_reader = data.grib2_cache_timestamps.read().await; let lct_reader = data.grib2_cache_timestamps.read().await;
if let Some(grib2) = data.grib2_cache.read().await.get(&settings.data) { if let Some(grib2) = data.grib2_cache.read().await.get(&settings.data) {
crate::sources::grib2::render(path.1 as f64, path.2 as f64, path.0, 256, wxbox_pal::parser::parse(&known_source.palette).unwrap(), grib2, known_source.missing, known_source.range_folded, known_source.no_coverage, &settings).await crate::sources::grib2::render(
path.1 as f64,
path.2 as f64,
path.0,
256,
wxbox_pal::parser::parse(&known_source.palette).unwrap(),
grib2,
known_source.missing,
known_source.range_folded,
known_source.no_coverage,
&settings,
)
.await
} else { } else {
debug!("not found grib2 after reload in base cache"); debug!("not found grib2 after reload in base cache");
return HttpResponse::new(StatusCode::NOT_FOUND) return HttpResponse::new(StatusCode::NOT_FOUND);
} }
} else { } else {
debug!("not found grib2 in configuration"); debug!("not found grib2 in configuration");
return HttpResponse::new(StatusCode::NOT_FOUND) return HttpResponse::new(StatusCode::NOT_FOUND);
} }
} else { } else {
debug!("not found datalayer registry"); debug!("not found datalayer registry");
return HttpResponse::new(StatusCode::NOT_FOUND) return HttpResponse::new(StatusCode::NOT_FOUND);
}; };
let image = merge(base_layer, data_layer); let image = merge(base_layer, data_layer);
@ -358,11 +444,13 @@ pub async fn source(path: actix_web::web::Path<(i32, u32, u32)>, req: Query<Quer
(0.31270, 0.32900), (0.31270, 0.32900),
(0.64000, 0.33000), (0.64000, 0.33000),
(0.30000, 0.60000), (0.30000, 0.60000),
(0.15000, 0.06000) (0.15000, 0.06000),
); );
encoder.set_source_chromaticities(source_chromaticities); encoder.set_source_chromaticities(source_chromaticities);
let mut writer = encoder.write_header().unwrap(); let mut writer = encoder.write_header().unwrap();
writer.write_image_data(&image.to_raw()).expect("failed to encode png"); writer
.write_image_data(&image.to_raw())
.expect("failed to encode png");
writer.finish().unwrap(); writer.finish().unwrap();
} }

View file

@ -1,29 +1,29 @@
mod toggle_switch; mod toggle_switch;
use crate::toggle_switch::toggle;
use egui::Context;
use egui::{Align2, CollapsingHeader, Color32, Frame, UiBuilder, Window};
use std::collections::HashMap; use std::collections::HashMap;
use std::env::var; use std::env::var;
use egui::{Align2, CollapsingHeader, Color32, Frame, UiBuilder, Window};
use egui::Context;
use walkers::{HttpOptions, HttpTiles, MapMemory, Position, TileId, Tiles};
use walkers::sources::{Attribution, TileSource}; use walkers::sources::{Attribution, TileSource};
use walkers::{HttpOptions, HttpTiles, MapMemory, Position, TileId, Tiles};
use wxbox_common::TileRequestOptions; use wxbox_common::TileRequestOptions;
use crate::toggle_switch::toggle;
pub struct WxboxApp { pub struct WxboxApp {
provider: HttpTiles, provider: HttpTiles,
map_memory: MapMemory, map_memory: MapMemory,
tile_request_options: TileRequestOptions, tile_request_options: TileRequestOptions,
rf_color: Color32 rf_color: Color32,
} }
pub struct DynamicUrlSource { pub struct DynamicUrlSource {
pub url_query: String pub url_query: String,
} }
impl DynamicUrlSource { impl DynamicUrlSource {
pub fn new_from(options: &TileRequestOptions) -> Self { pub fn new_from(options: &TileRequestOptions) -> Self {
Self { Self {
url_query: serde_json::to_string(options).unwrap() url_query: serde_json::to_string(options).unwrap(),
} }
} }
} }
@ -32,7 +32,9 @@ impl TileSource for DynamicUrlSource {
format!( format!(
"{}/{}/{}/{}.png?settings={}", "{}/{}/{}/{}.png?settings={}",
env!("TILER_BASE_URL"), env!("TILER_BASE_URL"),
tile_id.zoom, tile_id.x, tile_id.y, tile_id.zoom,
tile_id.x,
tile_id.y,
self.url_query self.url_query
) )
} }
@ -42,12 +44,11 @@ impl TileSource for DynamicUrlSource {
text: "OpenStreetMap contributors, NOAA, wxbox", text: "OpenStreetMap contributors, NOAA, wxbox",
url: "https://copyright.wxbox.e3t.cc", url: "https://copyright.wxbox.e3t.cc",
logo_light: None, logo_light: None,
logo_dark: None logo_dark: None,
} }
} }
} }
impl WxboxApp { impl WxboxApp {
pub fn new(ctx: Context) -> Self { pub fn new(ctx: Context) -> Self {
egui_extras::install_image_loaders(&ctx); egui_extras::install_image_loaders(&ctx);
@ -72,11 +73,11 @@ impl WxboxApp {
}, },
user_agent: None, user_agent: None,
}, },
ctx.clone() ctx.clone(),
), ),
map_memory: MapMemory::default(), map_memory: MapMemory::default(),
tile_request_options: req_options, tile_request_options: req_options,
rf_color: Color32::from_hex("#8d00a0ff").unwrap() rf_color: Color32::from_hex("#8d00a0ff").unwrap(),
} }
} }
} }

View file

@ -5,6 +5,6 @@ fn main() -> Result<(), eframe::Error> {
eframe::run_native( eframe::run_native(
"wxbox", "wxbox",
Default::default(), Default::default(),
Box::new(|cc| Ok(Box::new(WxboxApp::new(cc.egui_ctx.clone())))) Box::new(|cc| Ok(Box::new(WxboxApp::new(cc.egui_ctx.clone())))),
) )
} }

View file

@ -1,5 +1,5 @@
use wxbox_client::WxboxApp;
use web_sys::wasm_bindgen::JsCast; use web_sys::wasm_bindgen::JsCast;
use wxbox_client::WxboxApp;
fn main() { fn main() {
console_error_panic_hook::set_once(); console_error_panic_hook::set_once();
@ -22,7 +22,7 @@ fn main() {
.start( .start(
canvas, canvas,
web_options, web_options,
Box::new(|cc| Ok(Box::new(WxboxApp::new(cc.egui_ctx.clone())))) Box::new(|cc| Ok(Box::new(WxboxApp::new(cc.egui_ctx.clone())))),
) )
.await .await
.expect("failed to start eframe") .expect("failed to start eframe")

View file

@ -8,5 +8,5 @@ pub struct TileRequestOptions {
pub data_transparency: f64, pub data_transparency: f64,
pub show_range_folded: bool, pub show_range_folded: bool,
pub range_folded_color: u32 pub range_folded_color: u32,
} }