feat: add parser for RV data

This commit is contained in:
Luca 2025-06-04 23:46:40 +02:00
parent e0d64e4cbe
commit c79c471415
4 changed files with 245 additions and 31 deletions

4
src/data/mod.rs Normal file
View File

@ -0,0 +1,4 @@
mod parser;
mod rv;
pub use rv::Rv;

92
src/data/parser.rs Normal file
View File

@ -0,0 +1,92 @@
use std::io::{Error as IoError, Read, Seek};
use std::num::ParseIntError;
use std::slice;
use std::str::{self, FromStr, Utf8Error};
#[allow(dead_code)]
#[derive(Debug)]
pub enum ParseError {
IoError(IoError),
ParseIntError(ParseIntError),
Unexpected(String, &'static str),
Utf8Error(Utf8Error),
}
pub struct Parser<R: Read + Seek> {
r: R,
}
impl<R: Read + Seek> Parser<R> {
pub fn new(r: R) -> Self {
Self { r }
}
pub fn take(&mut self, length: usize) -> Result<String, ParseError> {
let mut buf = vec![0; length];
self.r
.read_exact(&mut buf)
.map_err(|err| ParseError::IoError(err))?;
Ok(String::from_utf8(buf).map_err(|err| ParseError::Utf8Error(err.utf8_error()))?)
}
pub fn peek(&mut self, pos: i64, length: usize) -> Result<String, ParseError> {
if pos != 0 {
self.r
.seek_relative(pos)
.map_err(|err| ParseError::IoError(err))?;
}
let string = self.take(length)?;
self.r
.seek_relative(-(length as i64) - pos)
.map_err(|err| ParseError::IoError(err))?;
Ok(string)
}
pub fn expect(&mut self, expected: &'static str) -> Result<(), ParseError> {
let actual = self.take(expected.len())?;
if actual == expected {
Ok(())
} else {
Err(ParseError::Unexpected(actual, expected))
}
}
pub fn parse_int<I, const N: usize>(&mut self) -> Result<I, ParseError>
where
I: FromStr<Err = ParseIntError>,
{
let mut buf = [0u8; N];
self.r
.read_exact(&mut buf)
.map_err(|err| ParseError::IoError(err))?;
I::from_str(
str::from_utf8(&buf)
.map_err(|err| ParseError::Utf8Error(err))?
.trim_ascii_start(),
)
.map_err(|err| ParseError::ParseIntError(err))
}
pub fn consume<T, const N: usize>(mut self) -> Result<Vec<T>, ParseError>
where
T: Clone + Default,
{
let mut data = vec![T::default(); N];
let buf =
unsafe { slice::from_raw_parts_mut(data.as_mut_ptr() as *mut _, N * size_of::<T>()) };
self.r
.read_exact(buf)
.map_err(|err| ParseError::IoError(err))?;
Ok(data)
}
}

132
src/data/rv.rs Normal file
View File

@ -0,0 +1,132 @@
#[cfg(not(target_endian = "little"))]
compile_error!("Only little-endian architectures are supported.");
use crate::data::parser::{ParseError, Parser};
use std::io::{Read, Seek};
const DATA_LEN: usize = 1100 * 1200;
#[derive(Clone, Copy, Debug)]
pub struct VersionMismatch {
pub actual: u8,
pub expected: u8,
}
#[derive(Clone, Copy, Debug)]
pub struct Datetime {
pub year: u16,
pub month: u8,
pub day: u8,
pub hour: u8,
pub minute: u8,
}
#[derive(Debug)]
pub struct Rv {
timestamp: Datetime,
version: u8,
precision: i8,
forecast: u16,
data: Vec<u16>,
}
impl Rv {
pub fn parse<R: Read + Seek>(r: R) -> Result<Self, ParseError> {
let mut parser = Parser::new(r);
parser.expect("RV")?;
let day = parser.parse_int::<_, 2>()?;
let hour = parser.parse_int::<_, 2>()?;
let minute = parser.parse_int::<_, 2>()?;
let _wmo = parser.parse_int::<u32, 5>()?;
let month = parser.parse_int::<_, 2>()?;
let year = 2000 + parser.parse_int::<u16, 2>()?;
parser.expect("BY")?;
let _len = if parser.peek(7, 2)? == "VS" {
parser.parse_int::<usize, 7>()?
} else {
parser.parse_int::<usize, 10>()?
};
parser.expect("VS")?;
let version = parser.parse_int::<_, 2>()?;
parser.expect("SW ")?;
let _sw_version = parser.take(8)?;
parser.expect("PR E")?;
let precision = parser.parse_int::<_, 3>()?;
parser.expect("INT")?;
let _interval = parser.parse_int::<u16, 4>()?;
parser.expect("GP")?;
let _resolution = parser.take(9)?;
parser.expect("VV ")?;
let forecast = parser.parse_int::<_, 3>()?;
parser.expect("MF ")?;
let _flags = parser.parse_int::<u32, 8>()?;
parser.expect("MS")?;
let text_len = parser.parse_int::<_, 3>()?;
let _text = parser.take(text_len)?;
parser.expect("\x03")?;
let data = parser.consume::<_, DATA_LEN>()?;
Ok(Self {
timestamp: Datetime {
year,
month,
day,
hour,
minute,
},
version,
precision,
forecast,
data,
})
}
pub fn expect_version(&self, version: u8) -> Result<(), VersionMismatch> {
if self.version == version {
Ok(())
} else {
Err(VersionMismatch {
actual: self.version,
expected: version,
})
}
}
pub fn scale(&self) -> f64 {
10_f64.powi(self.precision as i32)
}
pub fn instant(&self) -> (Datetime, u16) {
(self.timestamp, self.forecast)
}
pub fn data(&self) -> &[u16] {
&self.data[..]
}
}

View File

@ -1,45 +1,31 @@
use std::fs; mod data;
use data::Rv;
use image::imageops; use image::imageops;
use image::GrayAlphaImage; use image::GrayAlphaImage;
use proj::Proj; use proj::Proj;
use std::fs::File;
use std::io::BufReader;
const PROJ_WGS84_DE1200: &'static str = "+proj=stere +lat_0=90 +lat_ts=60 +lon_0=10 +a=6378137 +b=6356752.3142451802 +no_defs +x_0=543196.83521776402 +y_0=3622588.8619310018"; const PROJ_WGS84_DE1200: &'static str = "+proj=stere +lat_0=90 +lat_ts=60 +lon_0=10 +a=6378137 +b=6356752.3142451802 +no_defs +x_0=543196.83521776402 +y_0=3622588.8619310018";
enum State {
Header,
Lsb,
Msb(u8),
}
fn main() { fn main() {
let mut state = State::Header; let rv = Rv::parse(BufReader::new(File::open("input").unwrap())).unwrap();
rv.expect_version(5).unwrap();
let mut data = Vec::with_capacity(1100 * 1200 * 2); let mut data = Vec::with_capacity(1100 * 1200 * 2);
for b in fs::read("input").unwrap() { for v in rv.data() {
match state { if *v == 0x29c4 {
State::Header if b == 3 => { data.push(0);
state = State::Lsb; data.push(255);
} } else {
State::Header => (), data.push(if *v > 0 { 255 } else { 127 });
State::Lsb => { data.push(255);
state = State::Msb(b); }
}
State::Msb(lsb) => {
let v = u16::from_le_bytes([lsb, b]);
if v == 0x29c4 {
data.push(0);
data.push(255);
} else {
data.push(if v > 0 { 255 } else { 127 });
data.push(255);
}
state = State::Lsb;
}
};
} }
let mut img = GrayAlphaImage::from_raw(1100, 1200, data).unwrap(); let mut img = GrayAlphaImage::from_raw(1100, 1200, data).unwrap();