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 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),
|
||||||
}
|
}
|
|
@ -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) => {
|
||||||
|
return match e.kind() {
|
||||||
std::io::ErrorKind::UnexpectedEof => break,
|
std::io::ErrorKind::UnexpectedEof => break,
|
||||||
_ => return Err(e.into())
|
_ => 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,8 +364,7 @@ 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
|
||||||
|
@ -360,21 +375,26 @@ impl GridpointPNGDataRepresentation {
|
||||||
let value = (self.reference_value + diff) * dig_factor;
|
let value = (self.reference_value + diff) * dig_factor;
|
||||||
Some(value)
|
Some(value)
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
24 => {
|
24 => {
|
||||||
if x >= i.width() || y >= i.height() {
|
if x >= i.width() || y >= i.height() {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
let datapoint_channels = i.as_rgb8().unwrap().get_pixel(x, y).0;
|
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 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)
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
32 => {
|
32 => {
|
||||||
if x >= i.width() || y >= i.height() {
|
if x >= i.width() || y >= i.height() {
|
||||||
None
|
None
|
||||||
|
@ -388,11 +408,10 @@ impl GridpointPNGDataRepresentation {
|
||||||
let value = (self.reference_value + diff) * dig_factor;
|
let value = (self.reference_value + diff) * dig_factor;
|
||||||
Some(value)
|
Some(value)
|
||||||
}
|
}
|
||||||
},
|
|
||||||
_ => panic!("unsupported bit depth")
|
|
||||||
}
|
}
|
||||||
|
_ => 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,7 +546,7 @@ 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> {
|
||||||
|
@ -556,7 +570,11 @@ impl LatLongGrid {
|
||||||
|
|
||||||
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));
|
||||||
|
@ -590,8 +608,12 @@ 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;
|
||||||
|
@ -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
|
||||||
|
|
|
@ -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> {
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
|
@ -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,
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,7 +48,6 @@ impl ColorPalette for Palette {
|
||||||
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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,22 +77,21 @@ 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,
|
||||||
|
@ -102,11 +101,9 @@ pub fn parse(pal_str: &str) -> Result<Palette, PaletteParseError> {
|
||||||
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,
|
||||||
|
@ -116,15 +113,18 @@ pub fn parse(pal_str: &str) -> Result<Palette, PaletteParseError> {
|
||||||
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))
|
||||||
|
)
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>,
|
||||||
}
|
}
|
|
@ -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,7 +32,7 @@ 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()
|
||||||
|
@ -42,6 +42,4 @@ async fn main() -> std::io::Result<()> {
|
||||||
.bind(("::", 8080))?
|
.bind(("::", 8080))?
|
||||||
.run()
|
.run()
|
||||||
.await
|
.await
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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]
|
||||||
|
|
|
@ -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 {
|
||||||
} else {
|
red: color_raw[0],
|
||||||
Color { red: 0, green: 0, blue: 0, alpha: 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) => {
|
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,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 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();
|
||||||
|
@ -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 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(
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
Color {
|
||||||
red: pix[0],
|
red: pix[0],
|
||||||
green: pix[1],
|
green: pix[1],
|
||||||
blue: pix[2],
|
blue: pix[2],
|
||||||
alpha: pix[3]
|
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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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())))),
|
||||||
)
|
)
|
||||||
}
|
}
|
|
@ -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")
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
Loading…
Add table
Reference in a new issue