From c79c471415d2335e5b6e5b4a02bb3de2d948dc98 Mon Sep 17 00:00:00 2001 From: Luca Date: Wed, 4 Jun 2025 23:46:40 +0200 Subject: [PATCH] feat: add parser for RV data --- src/data/mod.rs | 4 ++ src/data/parser.rs | 92 +++++++++++++++++++++++++++++++ src/data/rv.rs | 132 +++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 48 ++++++----------- 4 files changed, 245 insertions(+), 31 deletions(-) create mode 100644 src/data/mod.rs create mode 100644 src/data/parser.rs create mode 100644 src/data/rv.rs diff --git a/src/data/mod.rs b/src/data/mod.rs new file mode 100644 index 0000000..95eee43 --- /dev/null +++ b/src/data/mod.rs @@ -0,0 +1,4 @@ +mod parser; +mod rv; + +pub use rv::Rv; diff --git a/src/data/parser.rs b/src/data/parser.rs new file mode 100644 index 0000000..d91ba9f --- /dev/null +++ b/src/data/parser.rs @@ -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: R, +} + +impl Parser { + pub fn new(r: R) -> Self { + Self { r } + } + + pub fn take(&mut self, length: usize) -> Result { + 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 { + 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(&mut self) -> Result + where + I: FromStr, + { + 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(mut self) -> Result, 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::()) }; + self.r + .read_exact(buf) + .map_err(|err| ParseError::IoError(err))?; + + Ok(data) + } +} diff --git a/src/data/rv.rs b/src/data/rv.rs new file mode 100644 index 0000000..992c05d --- /dev/null +++ b/src/data/rv.rs @@ -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, +} + +impl Rv { + pub fn parse(r: R) -> Result { + 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::()?; + + let month = parser.parse_int::<_, 2>()?; + let year = 2000 + parser.parse_int::()?; + + parser.expect("BY")?; + + let _len = if parser.peek(7, 2)? == "VS" { + parser.parse_int::()? + } else { + parser.parse_int::()? + }; + + 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::()?; + + 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::()?; + + 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[..] + } +} diff --git a/src/main.rs b/src/main.rs index 248ae77..391cb59 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,45 +1,31 @@ -use std::fs; +mod data; + +use data::Rv; use image::imageops; use image::GrayAlphaImage; 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"; -enum State { - Header, - Lsb, - Msb(u8), -} - 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); - for b in fs::read("input").unwrap() { - match state { - State::Header if b == 3 => { - state = State::Lsb; - } - State::Header => (), - State::Lsb => { - 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; - } - }; + for v in rv.data() { + if *v == 0x29c4 { + data.push(0); + data.push(255); + } else { + data.push(if *v > 0 { 255 } else { 127 }); + data.push(255); + } } let mut img = GrayAlphaImage::from_raw(1100, 1200, data).unwrap();