Compare commits

..

4 Commits

7 changed files with 723 additions and 2 deletions

3
.gitignore vendored
View File

@ -1 +1,4 @@
/target
input
rv.png

428
Cargo.lock generated Normal file
View File

@ -0,0 +1,428 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "adler2"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
[[package]]
name = "autocfg"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967"
[[package]]
name = "bytemuck"
version = "1.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9134a6ef01ce4b366b50689c94f82c14bc72bc5d0386829828a2e2752ef7958c"
[[package]]
name = "byteorder-lite"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495"
[[package]]
name = "cc"
version = "1.2.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0fc897dc1e865cc67c0e05a836d9d3f1df3cbe442aa4a9473b18e12624a4951"
dependencies = [
"shlex",
]
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "cmake"
version = "0.1.54"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7caa3f9de89ddbe2c607f4101924c5abec803763ae9534e4f4d7d8f84aa81f0"
dependencies = [
"cc",
]
[[package]]
name = "crc32fast"
version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3"
dependencies = [
"cfg-if",
]
[[package]]
name = "errno"
version = "0.3.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cea14ef9355e3beab063703aa9dab15afd25f0667c341310c1e5274bb1d0da18"
dependencies = [
"libc",
"windows-sys",
]
[[package]]
name = "fdeflate"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c"
dependencies = [
"simd-adler32",
]
[[package]]
name = "filetime"
version = "0.2.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586"
dependencies = [
"cfg-if",
"libc",
"libredox",
"windows-sys",
]
[[package]]
name = "flate2"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ced92e76e966ca2fd84c8f7aa01a4aea65b0eb6648d72f7c8f3e2764a67fece"
dependencies = [
"crc32fast",
"miniz_oxide",
]
[[package]]
name = "image"
version = "0.25.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db35664ce6b9810857a38a906215e75a9c879f0696556a39f59c62829710251a"
dependencies = [
"bytemuck",
"byteorder-lite",
"num-traits",
"png",
]
[[package]]
name = "libc"
version = "0.2.172"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa"
[[package]]
name = "libredox"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d"
dependencies = [
"bitflags 2.9.1",
"libc",
"redox_syscall",
]
[[package]]
name = "libsqlite3-sys"
version = "0.32.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fbb8270bb4060bd76c6e96f20c52d80620f1d82a3470885694e41e0f81ef6fe7"
dependencies = [
"pkg-config",
"vcpkg",
]
[[package]]
name = "link-cplusplus"
version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a6f6da007f968f9def0d65a05b187e2960183de70c160204ecfccf0ee330212"
dependencies = [
"cc",
]
[[package]]
name = "linux-raw-sys"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12"
[[package]]
name = "miniz_oxide"
version = "0.8.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a"
dependencies = [
"adler2",
"simd-adler32",
]
[[package]]
name = "num-traits"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [
"autocfg",
]
[[package]]
name = "pkg-config"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
[[package]]
name = "png"
version = "0.17.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526"
dependencies = [
"bitflags 1.3.2",
"crc32fast",
"fdeflate",
"flate2",
"miniz_oxide",
]
[[package]]
name = "proc-macro2"
version = "1.0.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
dependencies = [
"unicode-ident",
]
[[package]]
name = "proj"
version = "0.30.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "58e0c01de214d7ea50ee6519969be9efdc6f0dc5ce6c64fbd4f054ea26d43ca4"
dependencies = [
"libc",
"num-traits",
"proj-sys",
"thiserror",
]
[[package]]
name = "proj-sys"
version = "0.26.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a208129995443a4c475464c33308274be1578993b0ad266c9139188ed0dc7e91"
dependencies = [
"cmake",
"flate2",
"libsqlite3-sys",
"link-cplusplus",
"pkg-config",
"tar",
]
[[package]]
name = "quote"
version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
dependencies = [
"proc-macro2",
]
[[package]]
name = "redox_syscall"
version = "0.5.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "928fca9cf2aa042393a8325b9ead81d2f0df4cb12e1e24cef072922ccd99c5af"
dependencies = [
"bitflags 2.9.1",
]
[[package]]
name = "rustix"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266"
dependencies = [
"bitflags 2.9.1",
"errno",
"libc",
"linux-raw-sys",
"windows-sys",
]
[[package]]
name = "shlex"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "simd-adler32"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe"
[[package]]
name = "syn"
version = "2.0.101"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "tar"
version = "0.4.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d863878d212c87a19c1a610eb53bb01fe12951c0501cf5a0d65f724914a667a"
dependencies = [
"filetime",
"libc",
"xattr",
]
[[package]]
name = "thiserror"
version = "2.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "2.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "unicode-ident"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
[[package]]
name = "vcpkg"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
[[package]]
name = "wetter"
version = "0.1.0"
dependencies = [
"image",
"proj",
]
[[package]]
name = "windows-sys"
version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_i686_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "xattr"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d65cbf2f12c15564212d48f4e3dfb87923d25d611f2aed18f4cb23f0413d89e"
dependencies = [
"libc",
"rustix",
]

View File

@ -4,3 +4,5 @@ version = "0.1.0"
edition = "2021"
[dependencies]
image = { version = "0.25.6", default-features = false, features = ["png"] }
proj = { version = "0.30.0", default-features = false }

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,3 +1,63 @@
fn main() {
println!("Hello, world!");
mod data;
use data::Rv;
use image::imageops;
use image::RgbaImage;
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";
#[repr(u32)]
enum Intensity {
None = 0xffffff00,
Light = 0x80d0d080,
Moderate = 0x50a0e080,
Heavy = 0x1030f080,
Unknown = 0xd0d0d080,
}
fn main() {
let rv = Rv::parse(BufReader::new(File::open("input").unwrap())).unwrap();
rv.expect_version(5).unwrap();
let mut data = Vec::with_capacity(1100 * 1200 * 4);
for v in rv.data() {
let intensity = if *v == 0x29c4 {
Intensity::Unknown
} else {
let mm_per_h = *v as f64 * rv.scale() * 12.0; // 12 * 5 min intervals per hour
if mm_per_h < 0.1 {
Intensity::None
} else if mm_per_h < 5.0 {
Intensity::Light
} else if mm_per_h < 15.0 {
Intensity::Moderate
} else {
Intensity::Heavy
}
};
data.extend_from_slice(&(intensity as u32).to_be_bytes());
}
let mut img = RgbaImage::from_raw(1100, 1200, data).unwrap();
imageops::flip_vertical_in_place(&mut img);
img.save("rv.png").unwrap();
let wgs84_to_de1200 = Proj::new(PROJ_WGS84_DE1200).unwrap();
println!(
"{:#?}",
wgs84_to_de1200
.project(
(1.463301510_f64.to_radians(), 55.86208711_f64.to_radians()),
false
)
.unwrap()
);
}