This commit is contained in:
parent
2927361680
commit
2a2f46a5ff
20 changed files with 453 additions and 309 deletions
26
.forgejo/workflows/ci.yml
Normal file
26
.forgejo/workflows/ci.yml
Normal 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
|
|
@ -1,7 +0,0 @@
|
|||
on: [push]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: docker
|
||||
steps:
|
||||
- run: echo All Good
|
|
@ -1,6 +1,6 @@
|
|||
use std::io;
|
||||
use image::ImageError;
|
||||
use png::DecodingError;
|
||||
use std::io;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
|
@ -38,5 +38,5 @@ pub enum GribError {
|
|||
#[error("image error")]
|
||||
PNGImageError(#[from] DecodingError),
|
||||
#[error("image error")]
|
||||
ImageError(#[from] ImageError)
|
||||
ImageError(#[from] ImageError),
|
||||
}
|
|
@ -2,16 +2,18 @@ pub mod error;
|
|||
mod nommer;
|
||||
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::io::{Cursor, Read};
|
||||
use std::time::Instant;
|
||||
use image::codecs::png::PngDecoder;
|
||||
use image::{DynamicImage, GenericImageView, ImageBuffer, ImageDecoder, ImageFormat, ImageReader, Luma};
|
||||
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 EDITION: u8 = 2;
|
||||
|
@ -59,9 +61,11 @@ impl GribMessage {
|
|||
loop {
|
||||
let length = match nommer.read_u32() {
|
||||
Ok(l) => l,
|
||||
Err(e) => return match e.kind() {
|
||||
Err(e) => {
|
||||
return match e.kind() {
|
||||
std::io::ErrorKind::UnexpectedEof => break,
|
||||
_ => return Err(e.into())
|
||||
_ => return Err(e.into()),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -74,22 +78,22 @@ impl GribMessage {
|
|||
match section_number {
|
||||
1 => {
|
||||
identification = Some(IdentificationSection::parse(length, &mut nommer)?);
|
||||
},
|
||||
}
|
||||
3 => {
|
||||
grid_definition = Some(GridDefinitionSection::parse(length, &mut nommer)?);
|
||||
},
|
||||
}
|
||||
4 => {
|
||||
product_definition = Some(ProductDefinitionSection::parse(length, &mut nommer)?);
|
||||
},
|
||||
product_definition =
|
||||
Some(ProductDefinitionSection::parse(length, &mut nommer)?);
|
||||
}
|
||||
5 => {
|
||||
data_representation = Some(DataRepresentationSection::parse(length, &mut nommer)?);
|
||||
},
|
||||
data_representation =
|
||||
Some(DataRepresentationSection::parse(length, &mut nommer)?);
|
||||
}
|
||||
6 => {
|
||||
bitmap = Some(BitmapSection::parse(length, &mut nommer)?);
|
||||
},
|
||||
7 => {
|
||||
data = Some(DataSection::parse(length, &mut nommer)?)
|
||||
},
|
||||
}
|
||||
7 => data = Some(DataSection::parse(length, &mut nommer)?),
|
||||
_ => {
|
||||
let _ = nommer.read_n((length - 5) as usize);
|
||||
warn!("unimplemented section # {} len {}", section_number, length)
|
||||
|
@ -100,7 +104,8 @@ impl GribMessage {
|
|||
let identification = identification.ok_or(GribError::MissingIdentification)?;
|
||||
let grid_definition = grid_definition.ok_or(GribError::MissingGridDefinition)?;
|
||||
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 data = data.ok_or(GribError::MissingData)?;
|
||||
|
||||
|
@ -119,14 +124,16 @@ impl GribMessage {
|
|||
product_definition,
|
||||
data_representation,
|
||||
bitmap,
|
||||
data
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn value_for(&self, coord: LatLong) -> Option<f32> {
|
||||
match self.grid_definition.image_coordinates(coord) {
|
||||
Some((i, j)) => self.data_representation.get_image_coordinate(i as u32, j as u32),
|
||||
None => None
|
||||
Some((i, j)) => self
|
||||
.data_representation
|
||||
.get_image_coordinate(i as u32, j as u32),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -146,7 +153,7 @@ pub struct IdentificationSection {
|
|||
pub second: u8,
|
||||
pub production_status: u8,
|
||||
pub processed_data_type: u8,
|
||||
pub reserved: Vec<u8>
|
||||
pub reserved: Vec<u8>,
|
||||
}
|
||||
impl IdentificationSection {
|
||||
fn parse<R: Read>(length: u32, nommer: &mut NomReader<R>) -> Result<Self, GribError> {
|
||||
|
@ -178,12 +185,11 @@ impl IdentificationSection {
|
|||
second,
|
||||
production_status,
|
||||
processed_data_type,
|
||||
reserved
|
||||
reserved,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct GridDefinitionSection {
|
||||
pub source: u8,
|
||||
|
@ -191,7 +197,7 @@ pub struct GridDefinitionSection {
|
|||
pub length_of_optional_number_list: u8,
|
||||
pub interpretation_of_number_list: u8,
|
||||
pub grid_definition_template_number: u16,
|
||||
pub grid_definition: GridDefinition
|
||||
pub grid_definition: GridDefinition,
|
||||
}
|
||||
impl GridDefinitionSection {
|
||||
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 grid_definition_template_number = nommer.read_u16()?;
|
||||
|
||||
|
||||
let grid_definition = match grid_definition_template_number {
|
||||
0 => GridDefinition::LatitudeLongitude(LatLongGrid::parse(length, nommer)?),
|
||||
_ => return Err(GribError::UnsupportedGrid(grid_definition_template_number)),
|
||||
|
@ -223,7 +228,7 @@ impl GridDefinitionSection {
|
|||
length_of_optional_number_list,
|
||||
interpretation_of_number_list,
|
||||
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 product_definition_template_number = nommer.read_u16()?;
|
||||
|
||||
|
||||
if number_of_coordinate_values_after_template != 0 {
|
||||
return Err(GribError::ListOfNumbersNotSupported);
|
||||
}
|
||||
|
||||
let product_definition = match product_definition_template_number {
|
||||
0 => ProductDefinition::HorizontalLayerAtPointInTime(HorizontalLayerInstantaneousProductDef::parse(length, nommer)?),
|
||||
_ => return Err(GribError::UnsupportedProductDefTmpl(product_definition_template_number))
|
||||
0 => ProductDefinition::HorizontalLayerAtPointInTime(
|
||||
HorizontalLayerInstantaneousProductDef::parse(length, nommer)?,
|
||||
),
|
||||
_ => {
|
||||
return Err(GribError::UnsupportedProductDefTmpl(
|
||||
product_definition_template_number,
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
number_of_coordinate_values_after_template,
|
||||
product_definition_template_number,
|
||||
product_definition
|
||||
product_definition,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -265,7 +275,7 @@ impl ProductDefinitionSection {
|
|||
pub struct DataRepresentationSection {
|
||||
pub number_of_data_points: u32,
|
||||
pub data_representation_template_number: u16,
|
||||
pub data_representation_template: DataRepresentationTemplate
|
||||
pub data_representation_template: DataRepresentationTemplate,
|
||||
}
|
||||
impl DataRepresentationSection {
|
||||
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 = match data_representation_template_number {
|
||||
41 => DataRepresentationTemplate::GridpointPNG(GridpointPNGDataRepresentation::parse(length, nommer)?),
|
||||
_ => return Err(GribError::UnsupportedDataRepresentationTemplate(data_representation_template_number))
|
||||
41 => DataRepresentationTemplate::GridpointPNG(GridpointPNGDataRepresentation::parse(
|
||||
length, nommer,
|
||||
)?),
|
||||
_ => {
|
||||
return Err(GribError::UnsupportedDataRepresentationTemplate(
|
||||
data_representation_template_number,
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
number_of_data_points,
|
||||
data_representation_template_number,
|
||||
data_representation_template
|
||||
data_representation_template,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -294,17 +310,17 @@ impl DataRepresentationSection {
|
|||
|
||||
#[derive(Debug)]
|
||||
pub enum DataRepresentationTemplate {
|
||||
GridpointPNG(GridpointPNGDataRepresentation)
|
||||
GridpointPNG(GridpointPNGDataRepresentation),
|
||||
}
|
||||
impl DataRepresentationTemplate {
|
||||
fn load_data(&mut self, data: Vec<u8>) -> Result<(), GribError> {
|
||||
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> {
|
||||
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 type_of_values: u8,
|
||||
|
||||
pub image: Option<DynamicImage>
|
||||
pub image: Option<DynamicImage>,
|
||||
}
|
||||
impl GridpointPNGDataRepresentation {
|
||||
fn parse<R: Read>(_length: u32, nommer: &mut NomReader<R>) -> Result<Self, GribError> {
|
||||
|
@ -332,7 +348,7 @@ impl GridpointPNGDataRepresentation {
|
|||
decimal_scale_factor,
|
||||
depth,
|
||||
type_of_values,
|
||||
image: None
|
||||
image: None,
|
||||
})
|
||||
}
|
||||
fn load_data(&mut self, data: Vec<u8>) -> Result<(), GribError> {
|
||||
|
@ -348,8 +364,7 @@ impl GridpointPNGDataRepresentation {
|
|||
|
||||
fn get_image_coordinate(&self, x: u32, y: u32) -> Option<f32> {
|
||||
match &self.image {
|
||||
Some(i) => {
|
||||
match self.depth {
|
||||
Some(i) => match self.depth {
|
||||
1 | 2 | 4 | 8 | 16 => {
|
||||
if x >= i.width() || y >= i.height() {
|
||||
None
|
||||
|
@ -360,21 +375,26 @@ impl GridpointPNGDataRepresentation {
|
|||
let value = (self.reference_value + diff) * dig_factor;
|
||||
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 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
|
||||
|
@ -388,11 +408,10 @@ impl GridpointPNGDataRepresentation {
|
|||
let value = (self.reference_value + diff) * dig_factor;
|
||||
Some(value)
|
||||
}
|
||||
},
|
||||
_ => panic!("unsupported bit depth")
|
||||
}
|
||||
_ => panic!("unsupported bit depth"),
|
||||
},
|
||||
None => None
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -400,7 +419,7 @@ impl GridpointPNGDataRepresentation {
|
|||
#[derive(Debug)]
|
||||
pub struct BitmapSection {
|
||||
pub indicator: u8,
|
||||
pub bitmap: Vec<u8>
|
||||
pub bitmap: Vec<u8>,
|
||||
}
|
||||
impl BitmapSection {
|
||||
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)?;
|
||||
Ok(Self {
|
||||
indicator,
|
||||
bitmap
|
||||
})
|
||||
Ok(Self { indicator, bitmap })
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DataSection {
|
||||
pub data: Vec<u8>
|
||||
pub data: Vec<u8>,
|
||||
}
|
||||
impl DataSection {
|
||||
fn parse<R: Read>(length: u32, nommer: &mut NomReader<R>) -> Result<Self, GribError> {
|
||||
let data = nommer.read_n((length - 5) as usize)?;
|
||||
Ok(Self {
|
||||
data
|
||||
})
|
||||
Ok(Self { data })
|
||||
}
|
||||
}
|
||||
impl Debug for DataSection {
|
||||
|
@ -454,7 +468,7 @@ pub struct HorizontalLayerInstantaneousProductDef {
|
|||
pub scaled_value_first_fixed_surface: u32,
|
||||
pub second_fixed_surface_type: u8,
|
||||
pub scale_factor_second_fixed_surface: u8,
|
||||
pub scaled_value_second_fixed_surface: u32
|
||||
pub scaled_value_second_fixed_surface: u32,
|
||||
}
|
||||
impl HorizontalLayerInstantaneousProductDef {
|
||||
fn parse<R: Read>(_length: u32, nommer: &mut NomReader<R>) -> Result<Self, GribError> {
|
||||
|
@ -495,12 +509,12 @@ impl HorizontalLayerInstantaneousProductDef {
|
|||
|
||||
#[derive(Debug)]
|
||||
pub enum GridDefinition {
|
||||
LatitudeLongitude(LatLongGrid)
|
||||
LatitudeLongitude(LatLongGrid),
|
||||
}
|
||||
impl GridDefinition {
|
||||
fn image_coordinates(&self, at: LatLong) -> Option<(u64, u64)> {
|
||||
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)]
|
||||
pub enum LatLongVectorRelativity {
|
||||
EasterlyAndNortherly,
|
||||
IncreasingXY
|
||||
IncreasingXY,
|
||||
}
|
||||
impl LatLongGrid {
|
||||
fn parse<R: Read>(length: u32, nommer: &mut NomReader<R>) -> Result<Self, GribError> {
|
||||
if length != 72 {
|
||||
return Err(GribError::ListOfNumbersNotSupported);
|
||||
}
|
||||
let shape_of_the_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_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 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 ni= nommer.read_u32()?; // number of points along a parallel
|
||||
let nj= nommer.read_u32()?; // number of points along a meridian
|
||||
let basic_angle_of_the_initial_production_domain= nommer.read_u32()?;
|
||||
let subdivisions_of_basic_angle= nommer.read_u32()?;
|
||||
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 resolution_and_component_flags= nommer.read_u8()?;
|
||||
let shape_of_the_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_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 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 ni = nommer.read_u32()?; // number of points along a parallel
|
||||
let nj = nommer.read_u32()?; // number of points along a meridian
|
||||
let basic_angle_of_the_initial_production_domain = nommer.read_u32()?;
|
||||
let subdivisions_of_basic_angle = nommer.read_u32()?;
|
||||
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 resolution_and_component_flags = nommer.read_u8()?;
|
||||
|
||||
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 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 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 dj= nommer.read_u32()? as f64 * 10.0_f64.powi(-6);
|
||||
let scanning_mode_flags= nommer.read_u8()?;
|
||||
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 di = 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()?;
|
||||
|
||||
Ok(Self {
|
||||
shape_of_the_earth,
|
||||
|
@ -590,10 +608,14 @@ impl LatLongGrid {
|
|||
}
|
||||
|
||||
fn image_coordinates(&self, at: LatLong) -> Option<(u64, u64)> {
|
||||
if at.lat > self.la1 || at.lat < self.la2 { return None; }
|
||||
if at.long < self.lo1 || at.long > self.lo2 { return None; }
|
||||
if at.lat > self.la1 || at.lat < self.la2 {
|
||||
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;
|
||||
|
||||
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 {
|
||||
if longitude > 180.0 {
|
||||
longitude - 360.0
|
||||
|
|
|
@ -2,13 +2,11 @@ use std::io::Read;
|
|||
|
||||
#[derive(Debug)]
|
||||
pub struct NomReader<R: Read> {
|
||||
inner: R
|
||||
inner: R,
|
||||
}
|
||||
impl<R: Read> NomReader<R> {
|
||||
pub fn new(inner: R) -> NomReader<R> {
|
||||
Self {
|
||||
inner
|
||||
}
|
||||
Self { inner }
|
||||
}
|
||||
|
||||
pub fn read_u8(&mut self) -> Result<u8, std::io::Error> {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct LatLong {
|
||||
pub lat: f64,
|
||||
pub long: f64
|
||||
pub long: f64,
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
pub mod parser;
|
||||
pub mod default_palettes;
|
||||
pub mod parser;
|
||||
|
||||
pub use ordered_float::OrderedFloat;
|
||||
|
||||
|
@ -8,7 +8,7 @@ pub struct Color {
|
|||
pub red: u8,
|
||||
pub green: u8,
|
||||
pub blue: u8,
|
||||
pub alpha: u8
|
||||
pub alpha: u8,
|
||||
}
|
||||
|
||||
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_rules! c {
|
||||
($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) => {
|
||||
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 {
|
||||
fn colorize(&self, at: f64) -> Color {
|
||||
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) {
|
||||
|
||||
let mapped_end = *raw_range.1 - *raw_range.0;
|
||||
let mapped_point = at - *raw_range.0;
|
||||
let t = mapped_point / mapped_end;
|
||||
|
@ -53,7 +62,7 @@ impl ColorPalette for Palette {
|
|||
red: 0,
|
||||
green: 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,
|
||||
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,
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use crate::{c, r, Palette};
|
||||
use std::num::{ParseFloatError, ParseIntError};
|
||||
use std::str::FromStr;
|
||||
use thiserror::Error;
|
||||
use crate::{c, Palette, r};
|
||||
|
||||
#[derive(Error, Debug, Clone)]
|
||||
pub enum PaletteParseError {
|
||||
|
@ -16,7 +16,7 @@ pub enum PaletteParseError {
|
|||
#[error("invalid int")]
|
||||
InvalidInt(#[from] ParseIntError),
|
||||
#[error("palette is empty")]
|
||||
PaletteEmpty
|
||||
PaletteEmpty,
|
||||
}
|
||||
|
||||
pub fn parse(pal_str: &str) -> Result<Palette, PaletteParseError> {
|
||||
|
@ -52,7 +52,7 @@ pub fn parse(pal_str: &str) -> Result<Palette, PaletteParseError> {
|
|||
} else {
|
||||
return Err(PaletteParseError::IncorrectColorCommandLength(tokens.len()));
|
||||
}
|
||||
},
|
||||
}
|
||||
"color4:" => {
|
||||
if tokens.len() == 6 {
|
||||
// color4: val r g b a (5 tkns + command = 6 total)
|
||||
|
@ -77,22 +77,21 @@ pub fn parse(pal_str: &str) -> Result<Palette, PaletteParseError> {
|
|||
} else {
|
||||
return Err(PaletteParseError::IncorrectColorCommandLength(tokens.len()));
|
||||
}
|
||||
},
|
||||
"product:" => {}, // valid but ignored
|
||||
"units:" => {}, // valid but ignored
|
||||
"step:" => {}, // valid but ignored
|
||||
unknown => return Err(PaletteParseError::UnknownCommand(unknown.to_string()))
|
||||
}
|
||||
"product:" => {} // valid but ignored
|
||||
"units:" => {} // valid but ignored
|
||||
"step:" => {} // valid but ignored
|
||||
unknown => return Err(PaletteParseError::UnknownCommand(unknown.to_string())),
|
||||
}
|
||||
}
|
||||
|
||||
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![];
|
||||
|
||||
intermediate_with_infinities_added.push(
|
||||
(
|
||||
intermediate_with_infinities_added.push((
|
||||
f64::NEG_INFINITY,
|
||||
parsed_data[0].1,
|
||||
parsed_data[0].2,
|
||||
|
@ -102,29 +101,30 @@ pub fn parse(pal_str: &str) -> Result<Palette, PaletteParseError> {
|
|||
parsed_data[0].2,
|
||||
parsed_data[0].3,
|
||||
parsed_data[0].4,
|
||||
)
|
||||
);
|
||||
));
|
||||
intermediate_with_infinities_added.append(&mut parsed_data);
|
||||
intermediate_with_infinities_added.push(
|
||||
(
|
||||
intermediate_with_infinities_added.push((
|
||||
f64::INFINITY,
|
||||
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].3,
|
||||
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].2,
|
||||
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].1,
|
||||
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].4,
|
||||
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].3,
|
||||
intermediate_with_infinities_added[intermediate_with_infinities_added.len() - 1].4,
|
||||
));
|
||||
|
||||
let mut output = vec![];
|
||||
|
||||
for range in intermediate_with_infinities_added.windows(2) {
|
||||
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)))
|
||||
)
|
||||
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),
|
||||
),
|
||||
))
|
||||
}
|
||||
|
||||
Ok(output)
|
||||
|
@ -138,7 +138,8 @@ mod tests {
|
|||
#[test]
|
||||
fn almanydesigns_radaromega_nws_default() {
|
||||
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: 20 16 255 8 10 126 3
|
||||
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: 75 255 255 255 184 184 184
|
||||
color: 85 184 184 184
|
||||
color: 95 184 184 184"#).unwrap(),
|
||||
color: 95 184 184 184"#
|
||||
)
|
||||
.unwrap(),
|
||||
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!(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))),
|
||||
|
@ -157,7 +163,10 @@ color: 95 184 184 184"#).unwrap(),
|
|||
(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!(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))
|
||||
)
|
||||
]
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
use std::collections::HashMap;
|
||||
use serde::Deserialize;
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct Config {
|
||||
pub sources: Sources
|
||||
pub sources: Sources,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct Sources {
|
||||
pub grib2: HashMap<String, Grib2Source>
|
||||
pub grib2: HashMap<String, Grib2Source>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
|
@ -19,5 +19,5 @@ pub struct Grib2Source {
|
|||
pub palette: String,
|
||||
pub missing: Option<f64>,
|
||||
pub range_folded: Option<f64>,
|
||||
pub no_coverage: Option<f64>
|
||||
pub no_coverage: Option<f64>,
|
||||
}
|
|
@ -1,24 +1,24 @@
|
|||
pub(crate) mod sources;
|
||||
|
||||
mod pixmap;
|
||||
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::env::args;
|
||||
use std::fmt::Debug;
|
||||
use std::fs;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::RwLock;
|
||||
use std::time::SystemTime;
|
||||
use actix_web::{App, HttpServer};
|
||||
use actix_web::web::Data;
|
||||
use tokio::sync::RwLock;
|
||||
use wxbox_grib2::GribMessage;
|
||||
use crate::config::Config;
|
||||
|
||||
pub struct AppState {
|
||||
grib2_cache: RwLock<HashMap<String, Arc<RwLock<GribMessage>>>>,
|
||||
grib2_cache_timestamps: RwLock<HashMap<String, SystemTime>>,
|
||||
config: Config
|
||||
config: Config,
|
||||
}
|
||||
|
||||
#[actix_web::main]
|
||||
|
@ -32,7 +32,7 @@ async fn main() -> std::io::Result<()> {
|
|||
let data = Data::new(AppState {
|
||||
grib2_cache: RwLock::new(HashMap::new()),
|
||||
grib2_cache_timestamps: RwLock::new(HashMap::new()),
|
||||
config
|
||||
config,
|
||||
});
|
||||
HttpServer::new(move || {
|
||||
App::new()
|
||||
|
@ -42,6 +42,4 @@ async fn main() -> std::io::Result<()> {
|
|||
.bind(("::", 8080))?
|
||||
.run()
|
||||
.await
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -1,15 +1,18 @@
|
|||
|
||||
|
||||
use wxbox_pal::Color;
|
||||
|
||||
pub struct Pixmap {
|
||||
data: [Color; 256 * 256]
|
||||
data: [Color; 256 * 256],
|
||||
}
|
||||
impl Pixmap {
|
||||
#[inline]
|
||||
pub fn new() -> 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]
|
||||
|
|
|
@ -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::f64::consts::PI;
|
||||
use std::io::{BufWriter, Cursor, Read};
|
||||
use std::ops::{Add, Div, Mul, Sub};
|
||||
use std::sync::Arc;
|
||||
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 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 crate::config::Grib2Source;
|
||||
use crate::pixmap::Pixmap;
|
||||
use wxbox_common::TileRequestOptions;
|
||||
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;
|
||||
|
||||
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 {
|
||||
return true;
|
||||
}
|
||||
|
@ -59,15 +66,24 @@ pub async fn load(url: &str, is_gzipped: bool) -> Vec<u8> {
|
|||
out
|
||||
}
|
||||
|
||||
|
||||
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>>>>) {
|
||||
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>>>>,
|
||||
) {
|
||||
if needs_reload(&lct_cache, lut_key, valid_for).await {
|
||||
let mut lct_writer = lct_cache.write().await;
|
||||
|
||||
let message = load(from, needs_gzip).await;
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
@ -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 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 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 lat = ((PI - TWO_PI * ty).exp().atan() * 2.0_f64 - HALF_PI).to_degrees();
|
||||
|
||||
let nearest = message.value_for(LatLong {
|
||||
lat,
|
||||
long: lon
|
||||
}).map(|u| u as f64);
|
||||
let nearest = message
|
||||
.value_for(LatLong { lat, long: lon })
|
||||
.map(|u| u as f64);
|
||||
|
||||
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) == missing => Color { red: 0, green: 0, blue: 0, alpha: 0 },
|
||||
Some(c) if Some(c) == no_coverage => Color {
|
||||
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 => {
|
||||
if options.show_range_folded {
|
||||
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] }
|
||||
} else {
|
||||
Color { red: 0, green: 0, blue: 0, alpha: 0 }
|
||||
Color {
|
||||
red: color_raw[0],
|
||||
green: color_raw[1],
|
||||
blue: color_raw[2],
|
||||
alpha: color_raw[3],
|
||||
}
|
||||
} else {
|
||||
Color {
|
||||
red: 0,
|
||||
green: 0,
|
||||
blue: 0,
|
||||
alpha: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
Some(value_at_pos) => {
|
||||
let mut c = pal.colorize(value_at_pos);
|
||||
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
|
||||
}
|
||||
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);
|
||||
|
@ -130,7 +181,7 @@ pub async fn render(xtile: f64, ytile: f64, z: i32, tilesize: usize, pal: Palett
|
|||
|
||||
#[derive(Deserialize)]
|
||||
struct QueryReq {
|
||||
settings: String
|
||||
settings: String,
|
||||
}
|
||||
|
||||
fn colorf64(i: u8) -> f64 {
|
||||
|
@ -145,7 +196,7 @@ pub struct ColorF64 {
|
|||
pub red: f64,
|
||||
pub green: f64,
|
||||
pub blue: f64,
|
||||
pub alpha: f64
|
||||
pub alpha: f64,
|
||||
}
|
||||
impl From<Color> for ColorF64 {
|
||||
fn from(value: Color) -> Self {
|
||||
|
@ -153,7 +204,7 @@ impl From<Color> for ColorF64 {
|
|||
red: colorf64(value.red),
|
||||
green: colorf64(value.green),
|
||||
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),
|
||||
green: coloru8(value.green),
|
||||
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,
|
||||
green: self.green + rhs.green,
|
||||
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,
|
||||
green: self.green - rhs.green,
|
||||
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,
|
||||
green: rhs.green - 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,
|
||||
blue: self.blue * 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,
|
||||
green: self.green * rhs.green,
|
||||
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,
|
||||
green: self.green / rhs.green,
|
||||
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,
|
||||
green: self.green / rhs,
|
||||
blue: self.blue / rhs,
|
||||
alpha: self.alpha / rhs
|
||||
alpha: self.alpha / rhs,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub fn merge(base: Pixmap, data: Pixmap) -> Pixmap {
|
||||
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 {
|
||||
new.set(x, y, c_b.into());
|
||||
} 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;
|
||||
|
||||
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")]
|
||||
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();
|
||||
|
||||
// todo: load the base layer from external source
|
||||
|
@ -285,10 +340,19 @@ pub async fn source(path: actix_web::web::Path<(i32, u32, u32)>, req: Query<Quer
|
|||
let base_layer: Pixmap = if settings.baselayer == "osm" {
|
||||
let client = ClientBuilder::new()
|
||||
.user_agent(format!("wxbox-tiler/{}", env!("CARGO_PKG_VERSION")))
|
||||
.build().unwrap();
|
||||
let body = client.get(format!("https://tile.openstreetmap.org/{}/{}/{}.png", path.0, path.1, path.2))
|
||||
.build()
|
||||
.unwrap();
|
||||
let body = client
|
||||
.get(format!(
|
||||
"https://tile.openstreetmap.org/{}/{}/{}.png",
|
||||
path.0, path.1, path.2
|
||||
))
|
||||
.send()
|
||||
.await.unwrap().bytes().await.unwrap();
|
||||
.await
|
||||
.unwrap()
|
||||
.bytes()
|
||||
.await
|
||||
.unwrap();
|
||||
let mut img = ImageReader::new(Cursor::new(body.to_vec()));
|
||||
img.set_format(ImageFormat::Png);
|
||||
let img = img.decode().unwrap();
|
||||
|
@ -298,18 +362,22 @@ pub async fn source(path: actix_web::web::Path<(i32, u32, u32)>, req: Query<Quer
|
|||
for x in 0..256_usize {
|
||||
for y in 0..256_usize {
|
||||
let pix = rgb.get_pixel(y as u32, x as u32);
|
||||
map.set(x, y, Color {
|
||||
map.set(
|
||||
x,
|
||||
y,
|
||||
Color {
|
||||
red: pix[0],
|
||||
green: pix[1],
|
||||
blue: pix[2],
|
||||
alpha: pix[3]
|
||||
});
|
||||
alpha: pix[3],
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
map
|
||||
} else {
|
||||
debug!("not found baselayer");
|
||||
return HttpResponse::new(StatusCode::NOT_FOUND)
|
||||
return HttpResponse::new(StatusCode::NOT_FOUND);
|
||||
};
|
||||
|
||||
// data layer
|
||||
|
@ -317,29 +385,47 @@ pub async fn source(path: actix_web::web::Path<(i32, u32, u32)>, req: Query<Quer
|
|||
// - 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(
|
||||
&known_source.from,
|
||||
known_source.needs_gzip,
|
||||
known_source.valid_for.into(),
|
||||
&settings.data,
|
||||
&data.grib2_cache_timestamps,
|
||||
&data.grib2_cache
|
||||
).await;
|
||||
&data.grib2_cache,
|
||||
)
|
||||
.await;
|
||||
let lct_reader = data.grib2_cache_timestamps.read().await;
|
||||
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 {
|
||||
debug!("not found grib2 after reload in base cache");
|
||||
return HttpResponse::new(StatusCode::NOT_FOUND)
|
||||
return HttpResponse::new(StatusCode::NOT_FOUND);
|
||||
}
|
||||
} else {
|
||||
debug!("not found grib2 in configuration");
|
||||
return HttpResponse::new(StatusCode::NOT_FOUND)
|
||||
return HttpResponse::new(StatusCode::NOT_FOUND);
|
||||
}
|
||||
} else {
|
||||
debug!("not found datalayer registry");
|
||||
return HttpResponse::new(StatusCode::NOT_FOUND)
|
||||
return HttpResponse::new(StatusCode::NOT_FOUND);
|
||||
};
|
||||
|
||||
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.64000, 0.33000),
|
||||
(0.30000, 0.60000),
|
||||
(0.15000, 0.06000)
|
||||
(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
|
||||
.write_image_data(&image.to_raw())
|
||||
.expect("failed to encode png");
|
||||
writer.finish().unwrap();
|
||||
}
|
||||
|
||||
|
|
|
@ -1,29 +1,29 @@
|
|||
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::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::{HttpOptions, HttpTiles, MapMemory, Position, TileId, Tiles};
|
||||
use wxbox_common::TileRequestOptions;
|
||||
use crate::toggle_switch::toggle;
|
||||
|
||||
pub struct WxboxApp {
|
||||
provider: HttpTiles,
|
||||
map_memory: MapMemory,
|
||||
tile_request_options: TileRequestOptions,
|
||||
|
||||
rf_color: Color32
|
||||
rf_color: Color32,
|
||||
}
|
||||
|
||||
pub struct DynamicUrlSource {
|
||||
pub url_query: String
|
||||
pub url_query: String,
|
||||
}
|
||||
impl DynamicUrlSource {
|
||||
pub fn new_from(options: &TileRequestOptions) -> 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!(
|
||||
"{}/{}/{}/{}.png?settings={}",
|
||||
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
|
||||
)
|
||||
}
|
||||
|
@ -42,12 +44,11 @@ impl TileSource for DynamicUrlSource {
|
|||
text: "OpenStreetMap contributors, NOAA, wxbox",
|
||||
url: "https://copyright.wxbox.e3t.cc",
|
||||
logo_light: None,
|
||||
logo_dark: None
|
||||
logo_dark: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl WxboxApp {
|
||||
pub fn new(ctx: Context) -> Self {
|
||||
egui_extras::install_image_loaders(&ctx);
|
||||
|
@ -72,11 +73,11 @@ impl WxboxApp {
|
|||
},
|
||||
user_agent: None,
|
||||
},
|
||||
ctx.clone()
|
||||
ctx.clone(),
|
||||
),
|
||||
map_memory: MapMemory::default(),
|
||||
tile_request_options: req_options,
|
||||
rf_color: Color32::from_hex("#8d00a0ff").unwrap()
|
||||
rf_color: Color32::from_hex("#8d00a0ff").unwrap(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,6 @@ fn main() -> Result<(), eframe::Error> {
|
|||
eframe::run_native(
|
||||
"wxbox",
|
||||
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())))),
|
||||
)
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
use wxbox_client::WxboxApp;
|
||||
use web_sys::wasm_bindgen::JsCast;
|
||||
use wxbox_client::WxboxApp;
|
||||
|
||||
fn main() {
|
||||
console_error_panic_hook::set_once();
|
||||
|
@ -22,7 +22,7 @@ fn main() {
|
|||
.start(
|
||||
canvas,
|
||||
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
|
||||
.expect("failed to start eframe")
|
||||
|
|
|
@ -8,5 +8,5 @@ pub struct TileRequestOptions {
|
|||
pub data_transparency: f64,
|
||||
|
||||
pub show_range_folded: bool,
|
||||
pub range_folded_color: u32
|
||||
pub range_folded_color: u32,
|
||||
}
|
Loading…
Add table
Reference in a new issue