grib/encoder.rs
1//! GRIB2 data encoder.
2//!
3//! # The complexity of GRIB2
4//!
5//! GRIB2 is a data format specifically designed to store grid-point values
6//! related to meteorological data. Therefore, it is easy to imagine that, in
7//! addition to the values at each grid point, information such as the latitude
8//! and longitude of each point, the date and time, and meteorological elements
9//! must also be included as metadata. Furthermore, even for grid-point values
10//! represented as arrays of real numbers, GRIB2 generally does not store
11//! floating-point values as-is. Instead, the values are rounded based on their
12//! precision and then compressed and stored as an array of integers--that is,
13//! discrete numerical values.
14//!
15//! Consequently, unlike highly versatile formats such as NetCDF and HDF, which
16//! allow for the flexible serialization of large amounts of numerical data, or
17//! Gzip and Xz, which can freely compress arbitrary data, GRIB2 is a format
18//! with specific constraints. On the other hand, GRIB2 is a format whose
19//! specifications prevent situations where "a value has been obtained but its
20//! meaning is unclear." It can also be described as a format capable of
21//! "representing" meteorological data by incorporating not only raw numerical
22//! values but also the complexities of the real world, such as missing values
23//! and precision information.
24//!
25//! # Goal of this module
26//!
27//! The goal of this module is to enable users to create GRIB2 data--which
28//! has many parameters and some configurable options within its
29//! constraints--using an API that is as user-friendly as possible (although
30//! some parts are currently not user-friendly).
31//!
32//! Internally, a single GRIB2 dataset (message) is composed of a collection of
33//! blocks called "sections." However, this module's high-level API avoids using
34//! the term "section" and instead focuses on "what information needs to be
35//! configured" and "how you want to represent the meteorological data."
36//!
37//! # High-level and low-level APIs
38//!
39//! This module provides 3 structs: [`SingleGrib2Message`],
40//! [`MultiGridGrib2Message`], and [`MultiProductGrib2Message`]. By providing
41//! all the necessary information to these structs (which may be a bit of a
42//! challenge), you can create GRIB2 messages. These structs constitute a
43//! high-level API designed to accommodate the most common use cases.
44//! If your use case does not fit these scenarios, you will need to implement
45//! the low-level API [`WriteGrib2Message`] and its sub-traits:
46//! [`WriteGrib2MessageIterL1`], [`WriteGrib2MessageIterL2`], and
47//! [`WriteGrib2MessageIterL3`].
48//!
49//! # Generating data sets comprising multiple elements
50//!
51//! It is rare to have only one type of grid point value data for a given time
52//! that you want to generate and provide. As shown below, in many cases, you
53//! will likely want to generate and provide multiple types of grid point value
54//! data together.
55//!
56//! - Different meteorological elements (temperature, pressure, humidity, wind
57//! speed in 2 directions)
58//! - Different forecast times (1 hour later, 2 hours later, 3 hours later)
59//! - Different altitude levels (1000 hPa, 925 hPa, 850 hPa)
60//!
61//! We will refer to this type of data here as "data sets comprising multiple
62//! elements." The term "element" here is used in a general sense and is not
63//! limited to meteorological elements.
64//!
65//! When representing data sets comprising multiple elements in GRIB2, there are
66//! 2 main options, or 3 when broken down in detail:
67//!
68//! - A. Include them in a single message
69//! - B. Create separate messages (but store them in a single file)
70//! - C. Create separate messages (and store them in separate files)
71//!
72//! If you wish to use option A, use [`MultiGridGrib2Message`] or
73//! [`MultiProductGrib2Message`] (or the low-level API) to include the set of
74//! multiple elements in a single message. If you want to implement options B or
75//! C, simply use [`SingleGrib2Message`] for each element. You can implement
76//! option B by writing to the same output destination consecutively, and option
77//! C by writing to separate output destinations.
78
79use std::cell::{Ref, RefCell};
80
81pub use complex::*;
82pub use grid::*;
83pub use message::*;
84pub use simple::*;
85
86use crate::def::grib2::template::param_set;
87
88/// GPV data encoder.
89pub struct GpvEncoder<'d> {
90 data: std::borrow::Cow<'d, [f64]>,
91 method: EncodingMethod,
92 encoded: RefCell<Option<GpvEncodeOutput>>,
93}
94
95impl<'d> GpvEncoder<'d> {
96 pub fn new(data: std::borrow::Cow<'d, [f64]>, method: EncodingMethod) -> Self {
97 Self {
98 data,
99 method,
100 encoded: RefCell::new(None),
101 }
102 }
103
104 pub fn get_encoded(&'_ self) -> Ref<'_, GpvEncodeOutput> {
105 if self.encoded.borrow().is_none() {
106 *self.encoded.borrow_mut() = Some(self.encode());
107 }
108
109 Ref::map(self.encoded.borrow(), |c| c.as_ref().unwrap())
110 }
111
112 fn encode(&self) -> GpvEncodeOutput {
113 let output = match &self.method {
114 EncodingMethod::SimplePacking(simple_packing_strategy) => {
115 let encoder = simple::Encoder::new(&self.data, simple_packing_strategy.clone());
116 GpvEncodeOutputInner::SimplePacking(encoder.encode())
117 }
118 EncodingMethod::ComplexPacking(
119 simple_packing_strategy,
120 complex_packing_strategy,
121 _spatial_differencing_option,
122 ) => {
123 let encoder = complex::Encoder::new(
124 &self.data,
125 simple_packing_strategy.clone(),
126 complex_packing_strategy.clone(),
127 );
128 GpvEncodeOutputInner::ComplexPacking(encoder.encode())
129 }
130 };
131 GpvEncodeOutput(output)
132 }
133}
134
135impl<'d> WriteGrib2PointValues for GpvEncoder<'d> {
136 fn data_sections_len(&self) -> usize {
137 let encoded = self.get_encoded();
138 encoded.section5_len() + encoded.section6_len() + encoded.section7_len()
139 }
140
141 fn write_data_sections(&self, buf: &mut [u8]) -> Result<usize, &'static str> {
142 let encoded = self.get_encoded();
143 let mut pos = 0;
144 pos += encoded.write_section5(&mut buf[pos..])?;
145 pos += encoded.write_section6(&mut buf[pos..])?;
146 pos += encoded.write_section7(&mut buf[pos..])?;
147 Ok(pos)
148 }
149}
150
151#[derive(Debug, PartialEq, Eq, Clone)]
152#[non_exhaustive]
153pub enum EncodingMethod {
154 /// Simple packing.
155 SimplePacking(SimplePackingStrategy),
156 /// Complex packing.
157 ComplexPacking(
158 SimplePackingStrategy,
159 ComplexPackingStrategy,
160 SpatialDifferencingOption,
161 ),
162}
163
164/// Data obtained through GPV encoding. Instances are typically used to write
165/// GRIB2 data via the methods defined in [`WriteGrib2DataSections`].
166#[derive(Debug)]
167pub struct GpvEncodeOutput(GpvEncodeOutputInner);
168
169impl GpvEncodeOutput {
170 /// Returns the parameter set.
171 pub fn params(&self) -> GpvEncodeParams<'_> {
172 match &self.0 {
173 GpvEncodeOutputInner::SimplePacking(encoded) => {
174 GpvEncodeParams::SimplePacking(encoded.params())
175 }
176 GpvEncodeOutputInner::ComplexPacking(encoded) => {
177 let (simple, complex) = encoded.params();
178 GpvEncodeParams::ComplexPacking(simple, complex)
179 }
180 }
181 }
182}
183
184impl WriteGrib2DataSections for GpvEncodeOutput {
185 fn section5_len(&self) -> usize {
186 match &self.0 {
187 GpvEncodeOutputInner::SimplePacking(encoded) => encoded.section5_len(),
188 GpvEncodeOutputInner::ComplexPacking(encoded) => encoded.section5_len(),
189 }
190 }
191
192 fn write_section5(&self, buf: &mut [u8]) -> Result<usize, &'static str> {
193 match &self.0 {
194 GpvEncodeOutputInner::SimplePacking(encoded) => encoded.write_section5(buf),
195 GpvEncodeOutputInner::ComplexPacking(encoded) => encoded.write_section5(buf),
196 }
197 }
198
199 fn section6_len(&self) -> usize {
200 match &self.0 {
201 GpvEncodeOutputInner::SimplePacking(encoded) => encoded.section6_len(),
202 GpvEncodeOutputInner::ComplexPacking(encoded) => encoded.section6_len(),
203 }
204 }
205
206 fn write_section6(&self, buf: &mut [u8]) -> Result<usize, &'static str> {
207 match &self.0 {
208 GpvEncodeOutputInner::SimplePacking(encoded) => encoded.write_section6(buf),
209 GpvEncodeOutputInner::ComplexPacking(encoded) => encoded.write_section6(buf),
210 }
211 }
212
213 fn section7_len(&self) -> usize {
214 match &self.0 {
215 GpvEncodeOutputInner::SimplePacking(encoded) => encoded.section7_len(),
216 GpvEncodeOutputInner::ComplexPacking(encoded) => encoded.section7_len(),
217 }
218 }
219
220 fn write_section7(&self, buf: &mut [u8]) -> Result<usize, &'static str> {
221 match &self.0 {
222 GpvEncodeOutputInner::SimplePacking(encoded) => encoded.write_section7(buf),
223 GpvEncodeOutputInner::ComplexPacking(encoded) => encoded.write_section7(buf),
224 }
225 }
226}
227
228#[non_exhaustive]
229pub enum GpvEncodeParams<'a> {
230 SimplePacking(&'a param_set::SimplePacking),
231 ComplexPacking(&'a param_set::SimplePacking, &'a param_set::ComplexPacking),
232}
233
234#[derive(Debug)]
235enum GpvEncodeOutputInner {
236 SimplePacking(simple::Encoded),
237 ComplexPacking(complex::Encoded),
238}
239
240// Since the name `Encode` is already in use on other branches currently under
241// development, and since this trait is private, we'll continue to use that name
242// for the time being.
243trait Encode {
244 type Output;
245
246 fn encode(&self) -> Self::Output;
247}
248
249mod bitmap;
250mod complex;
251mod grid;
252mod helpers;
253mod message;
254mod simple;
255mod writer;