Skip to main content

hpr_io/grib2/
mod.rs

1//! GRIB edition 2, the World Meteorological Organization's binary format for gridded weather:
2//! the fields NOAA's NOMADS grib filter cuts from GFS and RAP, and whole GFS files.
3//!
4//! A GRIB2 file is a run of messages. Each holds one or more fields: a grid (section 3), what the
5//! values are and at which level and time (section 4), how they are packed (section 5), which grid
6//! points have a value (section 6, the bitmap) and the packed values (section 7). [`parse`] reads
7//! the headers of every field and keeps the packed values borrowed, so reading a file costs memory
8//! in proportion to the number of fields, not their grids; [`Field::value`] unpacks one grid
9//! point, [`Field::values`] all of them. The layout is the WMO's *Manual on Codes*, WMO-No. 306,
10//! Volume I.2, FM 92 GRIB edition 2, and its code and flag tables.
11//!
12//! What is read, which covers what the grib filter serves from GFS and RAP, and NCEP's whole GFS
13//! files:
14//!
15//! | section | templates read |
16//! |---|---|
17//! | 3, grid | 3.0 latitude/longitude; 3.30 Lambert conformal, on a sphere, tangent cone, north pole on the plane |
18//! | 4, product | 4.0, a field at a level at one time; 4.8, the same over a time interval (one time range) |
19//! | 5, packing | 5.0, simple packing; 5.2, complex packing; 5.3, complex packing with spatial differencing; 5.40, JPEG 2000, lossless, to 21 bits, as NCEP codes it |
20//! | 6, bitmap | none, one given, or the one before it in the message |
21//!
22//! Anything else is refused with [`Grib2Error::Unsupported`], naming the template, never read
23//! wrongly.
24//!
25//! **Values.** Simple packing stores each value as an integer `X` of a fixed number of bits, with
26//! a reference value `R` (a 32-bit float), a binary scale factor `E` and a decimal scale factor
27//! `D` shared by the field (WMO-No. 306, Regulation 92.9.4):
28//!
29//! `Y = (R + X · 2^E) / 10^D`
30//!
31//! evaluated in `f64` with exact powers of two and ten. A field of 0 bits is `R / 10^D` at every
32//! point with a value. Signed integers in GRIB2 are sign and magnitude (the first bit is the sign),
33//! not two's complement (Regulation 92.1.5).
34//!
35//! **Complex packing** (5.2, 5.3) splits the values into groups, each with its own reference and
36//! width, and may pack differences between neighbouring values instead of the values; the
37//! integers it rebuilds unpack by the same regulation. [`ComplexPacking`] has the details. Its
38//! values can only be read in order, so [`Field::value`] reads the field up to the point asked
39//! for; [`Field::values_at`] reads several points in one pass.
40//!
41//! **JPEG 2000** (5.40) codes the integers `X` as a greyscale image in a JPEG 2000 codestream,
42//! decoded by `hayro-jpeg2000`; [`Jpeg2000Packing`] has the limits. The image is decoded whole
43//! each time values are read, so read a field's values once with [`Field::values`] or
44//! [`Field::values_at`].
45//!
46//! **Grids.** [`Grid::point_deg`] gives a grid point's latitude and longitude, and
47//! [`Grid::index_at`] the (fractional) grid indices of a place. On a Lambert conformal grid both
48//! use the spherical Lambert conformal conic projection of Snyder, *Map Projections: A Working
49//! Manual*, USGS Professional Paper 1395 (1987): eqs. 14-1, 14-2, 14-4, 15-1 and 15-2, and for
50//! the inverse 14-9 to 14-11 and 15-5, with a tangent cone's `n = sin φ₁` (the one-parallel case
51//! of eq. 15-3). Winds on such a grid may be given along the
52//! grid's axes rather than east and north (flag table 3.3, bit 5, [`Grid::winds_grid_relative`]);
53//! [`Grid::earth_relative_wind`] turns them by the angle between the grid's `y` axis and true
54//! north, `θ = n (λ − λ₀)`.
55//!
56//! **Checked against:** ecCodes 2.49.0, run as an outside decoder, on recorded GFS and RAP cuts
57//! (`crates/hpr-net/tests/nomads.rs`): every value, and every grid point's latitude and longitude;
58//! on eight whole messages of a whole GFS file (`crates/hpr-io/tests/grib2_gfs.rs`); and, by a
59//! script run outside CI, on every value of that file, all 746,770,303 within 4.4e-16 of
60//! ecCodes' (`validation/oracles/grib2/gfs-whole-file.json`); and on four RAP messages in JPEG
61//! 2000 (`rap_messages_in_jpeg2000_decode_to_eccodes_values`).
62//!
63//! [roadmap]: https://github.com/nrdptel/hpr-sim/blob/main/docs/ROADMAP.md
64
65use std::sync::Arc;
66
67use serde::{Deserialize, Serialize};
68
69mod complex;
70mod jpeg2000;
71#[cfg(test)]
72mod tests;
73
74pub use complex::{ComplexPacking, SpatialDifferencing};
75pub use jpeg2000::{Jpeg2000Packing, MAX_BITS as MAX_JPEG2000_BITS, MAX_SIDE as MAX_JPEG2000_SIDE};
76
77/// The most grid points a field may have: 2²⁴, about 16.8 million. The largest common grids are
78/// well inside it (GFS at 0.25°, about 1.04 million; ECMWF at 0.1°, about 6.5 million). A field of
79/// 0 bits has no packed data to bound its grid, so this bounds what [`Field::values`] allocates:
80/// 16 bytes a point, up to 256 MiB for one field, whatever the file's size. [`Field::value`] reads
81/// one point of a simple-packed field and allocates nothing.
82pub const MAX_POINTS: u64 = 1 << 24;
83
84/// Why a GRIB2 file was refused.
85#[non_exhaustive]
86#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
87pub enum Grib2Error {
88    /// The bytes at a message's start are not `GRIB`.
89    #[error("not a GRIB message at byte {offset}: it begins with {head}")]
90    NotGrib {
91        /// Where the message was expected.
92        offset: usize,
93        /// Its first bytes, printed as hex.
94        head: String,
95    },
96    /// A GRIB message of another edition (edition 1 is the older format).
97    #[error("the message at byte {offset} is GRIB edition {edition}; only edition 2 is read")]
98    Edition {
99        /// The message's first byte.
100        offset: usize,
101        /// Its edition.
102        edition: u8,
103    },
104    /// A message or a section runs past the end of the bytes it has.
105    #[error("message {message} ends early: {what} needs {needed} bytes, {available} remain")]
106    Truncated {
107        /// The message's index, from 0.
108        message: usize,
109        /// What was being read.
110        what: &'static str,
111        /// Bytes needed.
112        needed: u64,
113        /// Bytes there are.
114        available: u64,
115    },
116    /// A message breaks the format.
117    #[error("message {message} is malformed: {reason}")]
118    Malformed {
119        /// The message's index, from 0.
120        message: usize,
121        /// What is wrong.
122        reason: String,
123    },
124    /// A template or an option the reader does not handle.
125    #[error("message {message}: {what} {value} is not read")]
126    Unsupported {
127        /// The message's index, from 0.
128        message: usize,
129        /// What it is, such as `"data representation template"`.
130        what: &'static str,
131        /// Its code.
132        value: u64,
133    },
134    /// A grid point asked for is outside the grid.
135    #[error("grid point {index} is outside a grid of {points}")]
136    PointOutside {
137        /// The index asked for.
138        index: u64,
139        /// The grid's points.
140        points: u64,
141    },
142}
143
144/// When a field's data starts: section 1's reference time, in UTC as written.
145#[non_exhaustive]
146#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
147pub struct ReferenceTime {
148    /// Code table 1.2: 0 analysis, 1 start of forecast, 2 verifying time, 3 observation time.
149    pub significance: u8,
150    /// Year.
151    pub year: u16,
152    /// Month, 1 to 12.
153    pub month: u8,
154    /// Day, 1 to 31.
155    pub day: u8,
156    /// Hour, 0 to 23.
157    pub hour: u8,
158    /// Minute.
159    pub minute: u8,
160    /// Second.
161    pub second: u8,
162}
163
164/// The figure of the Earth a grid is defined on (code table 3.2).
165#[non_exhaustive]
166#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
167pub enum Earth {
168    /// A sphere of this radius, m: codes 0 (6,367,470 m), 1 (given), 6 (6,371,229 m) and
169    /// 8 (6,371,200 m).
170    #[non_exhaustive]
171    Sphere {
172        /// Radius, m.
173        radius_m: f64,
174    },
175    /// An oblate spheroid or another figure, by its code; a Lambert grid on it is refused.
176    #[non_exhaustive]
177    Other {
178        /// The code in table 3.2.
179        code: u8,
180    },
181}
182
183/// How a grid's points map to the Earth.
184#[non_exhaustive]
185#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
186pub enum Projection {
187    /// Template 3.0: points evenly spaced in latitude and longitude.
188    #[non_exhaustive]
189    LatLon {
190        /// The first point's latitude, degrees north.
191        first_lat_deg: f64,
192        /// The first point's longitude, degrees east, in `[0, 360)` as GRIB writes it.
193        first_lon_deg: f64,
194        /// The spacing in longitude between neighbouring points along `i`, degrees.
195        di_deg: f64,
196        /// The spacing in latitude between neighbouring points along `j`, degrees.
197        dj_deg: f64,
198    },
199    /// Template 3.30: a Lambert conformal conic projection, on a tangent cone.
200    #[non_exhaustive]
201    LambertConformal {
202        /// The first point's latitude, degrees north.
203        first_lat_deg: f64,
204        /// The first point's longitude, degrees east.
205        first_lon_deg: f64,
206        /// The latitude where the cone touches the sphere (`Latin1 = Latin2 = LaD`), degrees.
207        tangent_lat_deg: f64,
208        /// The meridian parallel to the grid's `y` axis (`LoV`), degrees east.
209        orientation_lon_deg: f64,
210        /// The grid spacing along `x` at the tangent latitude, m.
211        dx_m: f64,
212        /// The grid spacing along `y` at the tangent latitude, m.
213        dy_m: f64,
214        /// The sphere's radius, m.
215        radius_m: f64,
216    },
217}
218
219/// A field's grid: its size, its projection and how its points are ordered.
220///
221/// Points are numbered `i + ni · j`, `i` along a row (east on a latitude/longitude grid, `+x` on a
222/// Lambert grid) and `j` from row to row. Rows run south to north when
223/// [`Grid::south_to_north`], else north to south; the reader refuses other scanning modes.
224#[non_exhaustive]
225#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
226pub struct Grid {
227    /// Points along a row.
228    pub ni: u32,
229    /// Rows.
230    pub nj: u32,
231    /// The figure of the Earth.
232    pub earth: Earth,
233    /// Whether rows run south to north (`+j`, scanning mode bit 2); otherwise north to south.
234    pub south_to_north: bool,
235    /// Whether vector components (winds) are along the grid's `x` and `y` axes rather than east
236    /// and north (flag table 3.3, bit 5).
237    pub winds_grid_relative: bool,
238    /// The projection.
239    pub projection: Projection,
240}
241
242/// A surface a field is on (code table 4.5), such as an isobaric level.
243#[non_exhaustive]
244#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
245pub struct Surface {
246    /// Its type: 1 the ground, 100 an isobaric level (value in Pa), 103 a height above the ground
247    /// (value in m), 255 none.
248    pub kind: u8,
249    /// Its value, `scaled value / 10^scale factor`, or `None` when missing.
250    pub value: Option<f64>,
251}
252
253/// What a field holds, and at which level and time: product definition template 4.0.
254#[non_exhaustive]
255#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
256pub struct Product {
257    /// The product definition template: 0, or 8 for a value over a time interval.
258    pub template: u16,
259    /// Parameter category (code table 4.1), such as 0 temperature or 2 momentum.
260    pub category: u8,
261    /// Parameter number within the category (code table 4.2).
262    pub number: u8,
263    /// Type of generating process (code table 4.3): 0 analysis, 2 forecast, and so on.
264    pub process: u8,
265    /// Unit of the forecast time (code table 4.4): 0 minute, 1 hour, 2 day, 10 3 h, 11 6 h,
266    /// 12 12 h, 13 second.
267    pub time_unit: u8,
268    /// Forecast time in that unit, after the reference time.
269    pub forecast_time: i64,
270    /// The first fixed surface.
271    pub surface: Surface,
272    /// The second fixed surface (kind 255 when there is none).
273    pub second_surface: Surface,
274    /// Template 4.8's time interval: the field is a statistic (such as an accumulation or an
275    /// average) from the forecast time to [`Statistics::end`]. `None` for template 4.0, a value at
276    /// one time.
277    pub statistics: Option<Statistics>,
278}
279
280/// A statistic over a time interval: product definition template 4.8, with one time range.
281#[non_exhaustive]
282#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
283pub struct Statistics {
284    /// The end of the interval, UTC.
285    pub end: ReferenceTime,
286    /// The statistic (code table 4.10): 0 average, 1 accumulation, 2 maximum, 3 minimum, and so on.
287    pub process: u8,
288    /// The unit of [`Statistics::length`] (code table 4.4).
289    pub time_unit: u8,
290    /// The interval's length in that unit.
291    pub length: i64,
292}
293
294impl Product {
295    /// The forecast time in seconds, or `None` for a unit other than those listed on
296    /// [`Product::time_unit`].
297    #[must_use]
298    pub fn forecast_time_s(&self) -> Option<i64> {
299        let unit_s = match self.time_unit {
300            0 => 60,
301            1 => 3_600,
302            2 => 86_400,
303            10 => 3 * 3_600,
304            11 => 6 * 3_600,
305            12 => 12 * 3_600,
306            13 => 1,
307            _ => return None,
308        };
309        self.forecast_time.checked_mul(unit_s)
310    }
311}
312
313/// Simple packing's parameters: data representation template 5.0.
314#[non_exhaustive]
315#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
316pub struct SimplePacking {
317    /// The reference value `R`, as written (a 32-bit float).
318    pub reference: f32,
319    /// The binary scale factor `E`.
320    pub binary_scale: i16,
321    /// The decimal scale factor `D`.
322    pub decimal_scale: i16,
323    /// Bits per packed value, 0 to 32.
324    pub bits: u8,
325    /// How many values are packed: the grid points the bitmap marks, or all of them.
326    pub count: u32,
327}
328
329impl SimplePacking {
330    /// `Y = (R + X · 2^E) / 10^D` for a packed integer `X`.
331    #[must_use]
332    pub fn unpack(&self, x: u32) -> f64 {
333        unpack(
334            self.reference,
335            self.binary_scale,
336            self.decimal_scale,
337            f64::from(x),
338        )
339    }
340}
341
342/// `Y = (R + X · 2^E) / 10^D`, WMO-No. 306 Regulation 92.9.4.
343fn unpack(reference: f32, binary_scale: i16, decimal_scale: i16, x: f64) -> f64 {
344    let scaled = f64::from(reference) + x * 2_f64.powi(binary_scale.into());
345    // Powers of ten to 10^22 are exact in f64, so dividing (or multiplying for D < 0) is one
346    // rounding, whichever sign D has; past 10^22 (a whole GFS file has D = 27 and 28), `powi`
347    // rounds too.
348    let d = i32::from(decimal_scale);
349    if d >= 0 {
350        scaled / 10_f64.powi(d)
351    } else {
352        scaled * 10_f64.powi(-d)
353    }
354}
355
356/// How a field's values are packed: data representation template 5.0, 5.2, 5.3 or 5.40.
357#[non_exhaustive]
358#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
359pub enum Packing {
360    /// Template 5.0.
361    Simple(SimplePacking),
362    /// Templates 5.2 and 5.3.
363    Complex(ComplexPacking),
364    /// Template 5.40.
365    Jpeg2000(Jpeg2000Packing),
366}
367
368impl Packing {
369    /// How many values are packed: the grid points the bitmap marks, or all of them.
370    #[must_use]
371    pub fn count(&self) -> u32 {
372        match self {
373            Self::Simple(p) => p.count,
374            Self::Complex(p) => p.count,
375            Self::Jpeg2000(p) => p.count,
376        }
377    }
378
379    /// The data representation template: 0, 2, 3 or 40.
380    #[must_use]
381    pub fn template(&self) -> u16 {
382        match self {
383            Self::Simple(_) => 0,
384            Self::Complex(p) if p.spatial_differencing.is_some() => 3,
385            Self::Complex(_) => 2,
386            Self::Jpeg2000(_) => 40,
387        }
388    }
389}
390
391/// One field of a GRIB2 file: its headers, with its bitmap and packed values borrowed from the
392/// file's bytes.
393///
394/// It holds no serde derive, since it borrows; its parts ([`Grid`], [`Product`] and the others)
395/// do.
396#[non_exhaustive]
397#[derive(Debug, Clone, PartialEq)]
398pub struct Field<'a> {
399    /// The message it is in, from 0.
400    pub message: usize,
401    /// The discipline (code table 0.0): 0 meteorological products.
402    pub discipline: u8,
403    /// The originating center (common code table C-11): 7 is NCEP.
404    pub center: u16,
405    /// The reference time.
406    pub reference_time: ReferenceTime,
407    /// The grid.
408    pub grid: Grid,
409    /// What the field is.
410    pub product: Product,
411    /// How it is packed.
412    pub packing: Packing,
413    /// One bit per grid point, first point in the first byte's high bit: 1 when the point has a
414    /// value. `None` when every point has one.
415    bitmap: Option<Bitmap<'a>>,
416    /// Section 7's data after its header: for simple packing the packed values, `bits` each,
417    /// from the first byte's high bit; for JPEG 2000 the codestream.
418    data: &'a [u8],
419    /// Where complex packing's parts start in `data`, with the packing `parse` checked them
420    /// against.
421    layout: Option<complex::Layout>,
422}
423
424impl Field<'_> {
425    /// The grid's points, `ni · nj`.
426    #[must_use]
427    pub fn points(&self) -> u64 {
428        u64::from(self.grid.ni) * u64::from(self.grid.nj)
429    }
430
431    /// The value at grid point `index` (numbered as on [`Grid`]), or `None` where the field has
432    /// none there. A complex-packed field's values can only be read in order, so this reads it
433    /// from its start up to the point, and a JPEG 2000 field's image is decoded whole: for more
434    /// than a few points, use [`Field::values_at`] or [`Field::values`], which read it once.
435    ///
436    /// # Errors
437    /// [`Grib2Error::PointOutside`] when `index` is not below [`Field::points`], and
438    /// [`Grib2Error::Malformed`] when a complex-packed field's differences overflow or a JPEG
439    /// 2000 codestream does not decode to the field's values.
440    pub fn value(&self, index: u64) -> Result<Option<f64>, Grib2Error> {
441        if let (Packing::Simple(p), None) = (&self.packing, &self.layout) {
442            let points = self.points();
443            if index >= points {
444                return Err(Grib2Error::PointOutside { index, points });
445            }
446            let k = match &self.bitmap {
447                Some(bitmap) if !bitmap.get(index) => return Ok(None),
448                Some(bitmap) => bitmap.ones_before(index),
449                None => index,
450            };
451            return Ok(Some(p.unpack(self.packed(p, k))));
452        }
453        Ok(self.values_at(&[index])?.first().copied().flatten())
454    }
455
456    /// The values at several grid points, in the order asked, reading a complex-packed field once
457    /// up to the last of them.
458    ///
459    /// # Errors
460    /// As [`Field::value`].
461    pub fn values_at(&self, indices: &[u64]) -> Result<Vec<Option<f64>>, Grib2Error> {
462        let points = self.points();
463        // Each point's place among the packed values, or `None` where the bitmap marks none.
464        let mut wanted = Vec::with_capacity(indices.len());
465        for &index in indices {
466            if index >= points {
467                return Err(Grib2Error::PointOutside { index, points });
468            }
469            wanted.push(match &self.bitmap {
470                Some(bitmap) => bitmap.get(index).then(|| bitmap.ones_before(index)),
471                None => Some(index),
472            });
473        }
474        match (&self.packing, &self.layout) {
475            (_, Some(layout)) => {
476                let mut order: Vec<(u64, usize)> = wanted
477                    .iter()
478                    .enumerate()
479                    .filter_map(|(k, w)| w.map(|w| (w, k)))
480                    .collect();
481                order.sort_unstable();
482                let end = order.last().map_or(0, |&(w, _)| w + 1);
483                let mut out = vec![None; indices.len()];
484                let mut next = 0;
485                complex::decode(layout, self.data, end, self.message, |position, value| {
486                    while let Some(&(w, k)) = order.get(next) {
487                        if w != position {
488                            break;
489                        }
490                        out[k] = value;
491                        next += 1;
492                    }
493                })?;
494                Ok(out)
495            }
496            (Packing::Simple(p), None) => Ok(wanted
497                .into_iter()
498                .map(|w| w.map(|k| p.unpack(self.packed(p, k))))
499                .collect()),
500            (Packing::Jpeg2000(p), None) => {
501                let x = jpeg2000::decode(p, self.data, self.message)?;
502                Ok(wanted
503                    .into_iter()
504                    // `check_field` held the bitmap's marks to `count`, and `decode` returns
505                    // `count` integers, so `k` is in range.
506                    .map(|w| w.and_then(|k| usize::try_from(k).ok().and_then(|k| x.get(k))))
507                    .map(|x| x.map(|&x| p.unpack(x)))
508                    .collect())
509            }
510            (Packing::Complex(_), None) => Err(malformed(self.message, "no layout")),
511        }
512    }
513
514    /// Every grid point's value, in grid order; `None` where the field has none. It allocates 16
515    /// bytes a point, up to 256 MiB at [`MAX_POINTS`], even for a field of 0 bits in a tiny file;
516    /// decoding a JPEG 2000 image takes more while it runs.
517    ///
518    /// # Errors
519    /// [`Grib2Error::Malformed`] when a complex-packed field's differences overflow or a JPEG
520    /// 2000 codestream does not decode to the field's values.
521    pub fn values(&self) -> Result<Vec<Option<f64>>, Grib2Error> {
522        let points = self.points();
523        // `parse` bounds `points` by `MAX_POINTS`, so this fits a `usize` on every target.
524        let mut out = Vec::with_capacity(usize::try_from(points).unwrap_or(0));
525        let marked = |index: u64| self.bitmap.as_ref().is_none_or(|bitmap| bitmap.get(index));
526        match (&self.packing, &self.layout) {
527            (_, Some(layout)) => {
528                // Each packed value goes to the next point the bitmap marks.
529                let mut index = 0;
530                complex::decode(layout, self.data, u64::MAX, self.message, |_, value| {
531                    while index < points && !marked(index) {
532                        out.push(None);
533                        index += 1;
534                    }
535                    out.push(value);
536                    index += 1;
537                })?;
538                out.resize(usize::try_from(points).unwrap_or(0), None);
539            }
540            (Packing::Simple(p), None) => {
541                let mut k = 0;
542                for index in 0..points {
543                    out.push(marked(index).then(|| {
544                        let x = self.packed(p, k);
545                        k += 1;
546                        p.unpack(x)
547                    }));
548                }
549            }
550            (Packing::Jpeg2000(p), None) => {
551                let mut x = jpeg2000::decode(p, self.data, self.message)?.into_iter();
552                for index in 0..points {
553                    // `decode` returns one integer per point the bitmap marks.
554                    out.push(if marked(index) {
555                        x.next().map(|x| p.unpack(x))
556                    } else {
557                        None
558                    });
559                }
560            }
561            (Packing::Complex(_), None) => return Err(malformed(self.message, "no layout")),
562        }
563        Ok(out)
564    }
565
566    /// The `k`-th packed integer of a simple-packed field. `parse` checked that the data holds
567    /// `count · bits` bits, and callers pass `k < count`.
568    fn packed(&self, p: &SimplePacking, k: u64) -> u32 {
569        // At most 32 bits.
570        u32::try_from(complex::bits(self.data, k * u64::from(p.bits), p.bits)).unwrap_or(u32::MAX)
571    }
572}
573
574/// A bitmap, one bit per grid point from the first byte's high bit, with its running count of set
575/// bits every 64 bytes: a point's place among the packed values then costs at most 64 bytes of
576/// counting, however large the grid, and fields that reuse the bitmap share the counts.
577#[derive(Debug, Clone, PartialEq)]
578struct Bitmap<'a> {
579    bits: &'a [u8],
580    /// `ones[k]`: the set bits in `bits[..64 k]`, one entry per 64 bytes and one more, so an
581    /// eighth of the bitmap's size.
582    ones: Arc<[u64]>,
583}
584
585impl<'a> Bitmap<'a> {
586    fn new(bits: &'a [u8]) -> Self {
587        let ones = std::iter::once(0)
588            .chain(bits.chunks(64).scan(0, |total, chunk| {
589                *total += count_ones(chunk);
590                Some(*total)
591            }))
592            .collect();
593        Self { bits, ones }
594    }
595
596    /// Whether bit `index` is set; `parse` checked the bitmap covers the grid.
597    fn get(&self, index: u64) -> bool {
598        let byte = usize::try_from(index / 8)
599            .ok()
600            .and_then(|i| self.bits.get(i))
601            .copied()
602            .unwrap_or(0);
603        byte & (0x80 >> (index % 8)) != 0
604    }
605
606    /// The set bits before bit `index`.
607    fn ones_before(&self, index: u64) -> u64 {
608        let byte = usize::try_from(index / 8)
609            .unwrap_or(usize::MAX)
610            .min(self.bits.len());
611        let block = byte / 64;
612        let mut n =
613            self.ones.get(block).copied().unwrap_or(0) + count_ones(&self.bits[block * 64..byte]);
614        let rest = index % 8;
615        if rest > 0 {
616            let partial = self.bits.get(byte).copied().unwrap_or(0) & !(0xFF_u8 >> rest);
617            n += u64::from(partial.count_ones());
618        }
619        n
620    }
621}
622
623fn count_ones(bytes: &[u8]) -> u64 {
624    bytes.iter().map(|b| u64::from(b.count_ones())).sum()
625}
626
627impl Grid {
628    /// The points, `ni · nj`.
629    #[must_use]
630    pub fn points(&self) -> u64 {
631        u64::from(self.ni) * u64::from(self.nj)
632    }
633
634    /// Grid point `index`'s latitude (degrees north) and longitude (degrees east, in `[0, 360)`),
635    /// or `None` outside the grid.
636    #[must_use]
637    pub fn point_deg(&self, index: u64) -> Option<(f64, f64)> {
638        if index >= self.points() {
639            return None;
640        }
641        let i = (index % u64::from(self.ni)) as f64;
642        let j = (index / u64::from(self.ni)) as f64;
643        let j_sign = if self.south_to_north { 1.0 } else { -1.0 };
644        match self.projection {
645            Projection::LatLon {
646                first_lat_deg,
647                first_lon_deg,
648                di_deg,
649                dj_deg,
650            } => Some((
651                first_lat_deg + j_sign * j * dj_deg,
652                (first_lon_deg + i * di_deg).rem_euclid(360.0),
653            )),
654            Projection::LambertConformal {
655                first_lat_deg,
656                first_lon_deg,
657                dx_m,
658                dy_m,
659                ..
660            } => {
661                let lambert = Lambert::new(&self.projection)?;
662                let (x0, y0) = lambert.forward(first_lat_deg, first_lon_deg);
663                let (lat, lon) = lambert.inverse(x0 + i * dx_m, y0 + j_sign * j * dy_m);
664                Some((lat, lon.rem_euclid(360.0)))
665            }
666        }
667    }
668
669    /// Whether a latitude/longitude grid's rows go all the way round the Earth: `ni` steps of
670    /// `di` make 360°, so the point after the last in a row is the row's first. A step is written
671    /// in millionths of a degree, so a 1/12° grid's 4,320 steps of 0.083333° make 359.9986°: the
672    /// test allows a millionth of a degree a step, for an encoder that truncates.
673    #[must_use]
674    pub fn circles_the_earth(&self) -> bool {
675        match self.projection {
676            Projection::LatLon { di_deg, .. } => {
677                let ni = f64::from(self.ni);
678                (ni * di_deg - 360.0).abs() <= ni * 1e-6
679            }
680            Projection::LambertConformal { .. } => false,
681        }
682    }
683
684    /// The fractional grid indices `(i, j)` of a place, such that grid point `(⌊i⌋, ⌊j⌋)` and its
685    /// neighbours at `+1` surround it; the place may be outside the grid.
686    #[must_use]
687    pub fn index_at(&self, latitude_deg: f64, longitude_deg: f64) -> (f64, f64) {
688        let j_sign = if self.south_to_north { 1.0 } else { -1.0 };
689        match self.projection {
690            Projection::LatLon {
691                first_lat_deg,
692                first_lon_deg,
693                di_deg,
694                dj_deg,
695            } => (
696                (longitude_deg - first_lon_deg).rem_euclid(360.0) / di_deg,
697                j_sign * (latitude_deg - first_lat_deg) / dj_deg,
698            ),
699            Projection::LambertConformal {
700                first_lat_deg,
701                first_lon_deg,
702                dx_m,
703                dy_m,
704                ..
705            } => match Lambert::new(&self.projection) {
706                Some(lambert) => {
707                    let (x0, y0) = lambert.forward(first_lat_deg, first_lon_deg);
708                    let (x, y) = lambert.forward(latitude_deg, longitude_deg);
709                    ((x - x0) / dx_m, j_sign * (y - y0) / dy_m)
710                }
711                None => (f64::NAN, f64::NAN),
712            },
713        }
714    }
715
716    /// The angle from true north to the grid's `+y` axis at a place, clockwise positive, rad:
717    /// `θ = n (λ − λ₀)` on a Lambert grid (Snyder eq. 14-4), 0 on a latitude/longitude grid,
718    /// whose axes point east and north. West of the central meridian `θ < 0`: the meridians lean
719    /// toward the cone's apex, so north is turned toward `+x` and the grid's `+y` west of north.
720    #[must_use]
721    pub fn north_to_grid_y_rad(&self, longitude_deg: f64) -> f64 {
722        match Lambert::new(&self.projection) {
723            Some(lambert) => lambert.theta(longitude_deg),
724            None => 0.0,
725        }
726    }
727
728    /// A wind's east and north components, m/s, from its components as the field gives them:
729    /// unchanged unless [`Grid::winds_grid_relative`], else turned from the grid's axes.
730    ///
731    /// On a Lambert grid true north is `(−sin θ, cos θ)` in grid axes and east `(cos θ, sin θ)`,
732    /// with `θ = n (λ − λ₀)`, so `u_east = u cos θ + v sin θ` and `v_north = −u sin θ + v cos θ`.
733    #[must_use]
734    pub fn earth_relative_wind(&self, u_m_s: f64, v_m_s: f64, longitude_deg: f64) -> (f64, f64) {
735        if !self.winds_grid_relative {
736            return (u_m_s, v_m_s);
737        }
738        let theta = self.north_to_grid_y_rad(longitude_deg);
739        let (sin, cos) = theta.sin_cos();
740        (u_m_s * cos + v_m_s * sin, -u_m_s * sin + v_m_s * cos)
741    }
742}
743
744/// The spherical Lambert conformal conic projection on a tangent cone (Snyder 1987, ch. 15).
745struct Lambert {
746    /// Cone constant `n = sin φ₁` (eq. 15-3 with one standard parallel).
747    n: f64,
748    /// `R F`, with `F = cos φ₁ tanⁿ(π/4 + φ₁/2) / n` (eq. 15-2).
749    rf: f64,
750    /// The central meridian `λ₀`, rad.
751    lon0_rad: f64,
752}
753
754impl Lambert {
755    fn new(projection: &Projection) -> Option<Self> {
756        let Projection::LambertConformal {
757            tangent_lat_deg,
758            orientation_lon_deg,
759            radius_m,
760            ..
761        } = *projection
762        else {
763            return None;
764        };
765        let phi1 = tangent_lat_deg.to_radians();
766        let n = phi1.sin();
767        let f = phi1.cos() * quarter_tan(phi1).powf(n) / n;
768        Some(Self {
769            n,
770            rf: radius_m * f,
771            lon0_rad: orientation_lon_deg.to_radians(),
772        })
773    }
774
775    /// `θ = n (λ − λ₀)`, with `λ − λ₀` folded into `[−π, π)` (eq. 14-4).
776    fn theta(&self, longitude_deg: f64) -> f64 {
777        let d = (longitude_deg.to_radians() - self.lon0_rad + std::f64::consts::PI)
778            .rem_euclid(std::f64::consts::TAU)
779            - std::f64::consts::PI;
780        self.n * d
781    }
782
783    /// `x = ρ sin θ`, `y = −ρ cos θ`, `ρ = R F / tanⁿ(π/4 + φ/2)` (eqs. 14-1, 14-2 and 15-1, with
784    /// the origin at the cone's apex, `ρ₀ = 0`; only differences are used).
785    fn forward(&self, latitude_deg: f64, longitude_deg: f64) -> (f64, f64) {
786        let rho = self.rf / quarter_tan(latitude_deg.to_radians()).powf(self.n);
787        let theta = self.theta(longitude_deg);
788        (rho * theta.sin(), -rho * theta.cos())
789    }
790
791    /// The inverse, `ρ = √(x² + y²)`, `θ = atan2(x, −y)`, `φ = 2 atan((R F/ρ)^(1/n)) − π/2`,
792    /// `λ = θ/n + λ₀` (eqs. 14-10, 14-11, 15-5 and 14-9, for `n > 0` and `ρ₀ = 0`), in degrees.
793    fn inverse(&self, x: f64, y: f64) -> (f64, f64) {
794        let rho = x.hypot(y);
795        let theta = x.atan2(-y);
796        let phi = 2.0 * (self.rf / rho).powf(1.0 / self.n).atan() - std::f64::consts::FRAC_PI_2;
797        (
798            phi.to_degrees(),
799            (theta / self.n + self.lon0_rad).to_degrees(),
800        )
801    }
802}
803
804/// `tan(π/4 + φ/2)`.
805fn quarter_tan(phi: f64) -> f64 {
806    (std::f64::consts::FRAC_PI_4 + phi / 2.0).tan()
807}
808
809/// Reads every field of a GRIB2 file.
810///
811/// The messages must follow one another with nothing between or after them, as the grib filter
812/// and NCEP write them.
813///
814/// # Errors
815/// [`Grib2Error::NotGrib`] and [`Grib2Error::Edition`] when a message is not GRIB2,
816/// [`Grib2Error::Truncated`] and [`Grib2Error::Malformed`] when one breaks the format, and
817/// [`Grib2Error::Unsupported`] for a template or an option outside the module's table.
818pub fn parse(bytes: &[u8]) -> Result<Vec<Field<'_>>, Grib2Error> {
819    let mut fields = Vec::new();
820    let mut offset = 0;
821    let mut message = 0;
822    while offset < bytes.len() {
823        let rest = &bytes[offset..];
824        if rest.len() < 16 || &rest[..4] != b"GRIB" {
825            return Err(Grib2Error::NotGrib {
826                offset,
827                head: rest.iter().take(8).map(|b| format!("{b:02x}")).collect(),
828            });
829        }
830        if rest[7] != 2 {
831            return Err(Grib2Error::Edition {
832                offset,
833                edition: rest[7],
834            });
835        }
836        let length = u64::from_be_bytes([
837            rest[8], rest[9], rest[10], rest[11], rest[12], rest[13], rest[14], rest[15],
838        ]);
839        let available = rest.len() as u64;
840        if length > available {
841            return Err(Grib2Error::Truncated {
842                message,
843                what: "the message",
844                needed: length,
845                available,
846            });
847        }
848        // `length ≤ rest.len()`, so it fits a `usize`.
849        let length = usize::try_from(length).unwrap_or(usize::MAX);
850        if length < 20 || &rest[length - 4..length] != b"7777" {
851            return Err(malformed(message, "it does not end with 7777"));
852        }
853        read_message(&rest[..length], message, rest[6], &mut fields)?;
854        offset += length;
855        message += 1;
856    }
857    Ok(fields)
858}
859
860fn malformed(message: usize, reason: impl Into<String>) -> Grib2Error {
861    Grib2Error::Malformed {
862        message,
863        reason: reason.into(),
864    }
865}
866
867/// Reads one message's sections, appending a [`Field`] at each section 7.
868fn read_message<'a>(
869    bytes: &'a [u8],
870    message: usize,
871    discipline: u8,
872    fields: &mut Vec<Field<'a>>,
873) -> Result<(), Grib2Error> {
874    let end = bytes.len() - 4;
875    let mut offset = 16;
876    let mut identification: Option<(u16, ReferenceTime)> = None;
877    let mut grid: Option<Grid> = None;
878    let mut product: Option<Product> = None;
879    let mut packing: Option<Packing> = None;
880    // `Some(None)`: section 6 said there is no bitmap.
881    let mut bitmap: Option<Option<Bitmap<'a>>> = None;
882    let mut last_bitmap: Option<Bitmap<'a>> = None;
883    let before = fields.len();
884    while offset < end {
885        if end - offset < 5 {
886            return Err(truncated(message, "a section's header", 5, end - offset));
887        }
888        let length = be_u32(&bytes[offset..offset + 4]) as usize;
889        let number = bytes[offset + 4];
890        if length < 5 {
891            return Err(malformed(
892                message,
893                format!("section {number} is {length} bytes long"),
894            ));
895        }
896        if length > end - offset {
897            return Err(truncated(message, "a section", length, end - offset));
898        }
899        let s = &bytes[offset..offset + length];
900        match number {
901            1 => identification = Some(read_identification(s, message)?),
902            2 => {}
903            3 => grid = Some(read_grid(s, message)?),
904            4 => product = Some(read_product(s, message)?),
905            5 => packing = Some(read_packing(s, message)?),
906            6 => {
907                let indicator = at(s, 5, message)?;
908                bitmap = Some(match indicator {
909                    0 => {
910                        last_bitmap = Some(Bitmap::new(&s[6..]));
911                        last_bitmap.clone()
912                    }
913                    254 => Some(last_bitmap.clone().ok_or_else(|| {
914                        malformed(message, "section 6 reuses a bitmap none gave")
915                    })?),
916                    255 => None,
917                    other => {
918                        return Err(Grib2Error::Unsupported {
919                            message,
920                            what: "bitmap indicator",
921                            value: other.into(),
922                        });
923                    }
924                });
925            }
926            7 => {
927                let (
928                    Some((center, reference_time)),
929                    Some(grid),
930                    Some(product),
931                    Some(packing),
932                    Some(bitmap),
933                ) = (
934                    identification,
935                    grid,
936                    product.take(),
937                    packing.take(),
938                    bitmap.take(),
939                )
940                else {
941                    return Err(malformed(
942                        message,
943                        "section 7 comes before sections 1 and 3 to 6 are all given",
944                    ));
945                };
946                let mut field = Field {
947                    message,
948                    discipline,
949                    center,
950                    reference_time,
951                    grid,
952                    product,
953                    packing,
954                    bitmap,
955                    data: &s[5..],
956                    layout: None,
957                };
958                check_field(&mut field)?;
959                fields.push(field);
960            }
961            other => {
962                return Err(malformed(message, format!("it holds a section {other}")));
963            }
964        }
965        offset += length;
966    }
967    if product.is_some() || packing.is_some() || bitmap.is_some() {
968        return Err(malformed(message, "it ends before a section 7"));
969    }
970    if fields.len() == before {
971        return Err(malformed(message, "it holds no field"));
972    }
973    Ok(())
974}
975
976/// Checks a field's bitmap and packed values are as long as its grid and packing say, and finds
977/// where a complex-packed field's parts start.
978fn check_field(field: &mut Field<'_>) -> Result<(), Grib2Error> {
979    let message = field.message;
980    let points = field.points();
981    let count = u64::from(field.packing.count());
982    match &field.bitmap {
983        Some(bitmap) => {
984            let have = bitmap.bits.len() as u64 * 8;
985            if have < points {
986                return Err(truncated(
987                    message,
988                    "the bitmap",
989                    points.div_ceil(8),
990                    bitmap.bits.len(),
991                ));
992            }
993            let marked = bitmap.ones_before(points);
994            if marked != count {
995                return Err(malformed(
996                    message,
997                    format!("the bitmap marks {marked} points but section 5 packs {count} values"),
998                ));
999            }
1000        }
1001        None if count != points => {
1002            return Err(malformed(
1003                message,
1004                format!("a grid of {points} points without a bitmap packs {count} values"),
1005            ));
1006        }
1007        None => {}
1008    }
1009    // `Y` rises with `X` (`2^E > 0`), so the ends bound every value: for simple packing `0` and
1010    // `2^bits − 1` (JPEG 2000's too); for complex packing, whose rebuilt integers the headers don't bound, the ends
1011    // of `i64`.
1012    #[allow(
1013        clippy::cast_precision_loss,
1014        reason = "the ends of i64 only need to be near, to bound the scale"
1015    )]
1016    let (low, high, e, d) = match &field.packing {
1017        Packing::Simple(p) => {
1018            let largest = u32::try_from((1_u64 << p.bits) - 1).unwrap_or(u32::MAX);
1019            (
1020                p.unpack(0),
1021                p.unpack(largest),
1022                p.binary_scale,
1023                p.decimal_scale,
1024            )
1025        }
1026        Packing::Complex(p) => (
1027            p.unpack(i64::MIN),
1028            p.unpack(i64::MAX),
1029            p.binary_scale,
1030            p.decimal_scale,
1031        ),
1032        Packing::Jpeg2000(p) => (
1033            p.unpack(0),
1034            p.unpack((1_u32 << p.bits) - 1),
1035            p.binary_scale,
1036            p.decimal_scale,
1037        ),
1038    };
1039    if !(low.is_finite() && high.is_finite()) {
1040        return Err(malformed(
1041            message,
1042            format!("the scale factors (binary {e}, decimal {d}) make values that are not finite"),
1043        ));
1044    }
1045    match &field.packing {
1046        Packing::Simple(p) => {
1047            let bits = count * u64::from(p.bits);
1048            let have = field.data.len() as u64 * 8;
1049            if have < bits {
1050                return Err(truncated(
1051                    message,
1052                    "the packed values",
1053                    bits.div_ceil(8),
1054                    field.data.len(),
1055                ));
1056            }
1057        }
1058        Packing::Complex(p) => field.layout = Some(complex::layout(p, field.data, message)?),
1059        Packing::Jpeg2000(p) => jpeg2000::check(p, field.data, message)?,
1060    }
1061    Ok(())
1062}
1063
1064fn truncated(
1065    message: usize,
1066    what: &'static str,
1067    needed: impl TryInto<u64>,
1068    available: impl TryInto<u64>,
1069) -> Grib2Error {
1070    Grib2Error::Truncated {
1071        message,
1072        what,
1073        needed: needed.try_into().unwrap_or(u64::MAX),
1074        available: available.try_into().unwrap_or(u64::MAX),
1075    }
1076}
1077
1078/// Byte `i` of a section, or [`Grib2Error::Truncated`].
1079fn at(s: &[u8], i: usize, message: usize) -> Result<u8, Grib2Error> {
1080    s.get(i)
1081        .copied()
1082        .ok_or_else(|| truncated(message, "a section", i + 1, s.len()))
1083}
1084
1085/// A section at least `len` bytes long, or [`Grib2Error::Truncated`].
1086fn need<'a>(
1087    s: &'a [u8],
1088    len: usize,
1089    what: &'static str,
1090    message: usize,
1091) -> Result<&'a [u8], Grib2Error> {
1092    if s.len() < len {
1093        return Err(truncated(message, what, len, s.len()));
1094    }
1095    Ok(s)
1096}
1097
1098fn be_u16(b: &[u8]) -> u16 {
1099    u16::from_be_bytes([b[0], b[1]])
1100}
1101
1102fn be_u32(b: &[u8]) -> u32 {
1103    u32::from_be_bytes([b[0], b[1], b[2], b[3]])
1104}
1105
1106/// A latitude, degrees, if it is on the Earth.
1107fn on_earth(latitude_deg: f64, message: usize) -> Result<f64, Grib2Error> {
1108    if latitude_deg.abs() <= 90.0 {
1109        Ok(latitude_deg)
1110    } else {
1111        Err(malformed(message, format!("a latitude of {latitude_deg}°")))
1112    }
1113}
1114
1115/// A sign-and-magnitude 32-bit integer (WMO-No. 306, Regulation 92.1.5).
1116fn be_i32(b: &[u8]) -> i64 {
1117    let raw = be_u32(b);
1118    let magnitude = i64::from(raw & 0x7FFF_FFFF);
1119    if raw & 0x8000_0000 != 0 {
1120        -magnitude
1121    } else {
1122        magnitude
1123    }
1124}
1125
1126/// A sign-and-magnitude 16-bit integer.
1127fn be_i16(b: &[u8]) -> i16 {
1128    let raw = be_u16(b);
1129    // The magnitude is at most 0x7FFF, so it fits.
1130    let magnitude = i16::try_from(raw & 0x7FFF).unwrap_or(i16::MAX);
1131    if raw & 0x8000 != 0 {
1132        -magnitude
1133    } else {
1134        magnitude
1135    }
1136}
1137
1138/// A sign-and-magnitude 8-bit integer.
1139fn be_i8(b: u8) -> i32 {
1140    let magnitude = i32::from(b & 0x7F);
1141    if b & 0x80 != 0 { -magnitude } else { magnitude }
1142}
1143
1144/// Section 1: the originating center and the reference time.
1145fn read_identification(s: &[u8], message: usize) -> Result<(u16, ReferenceTime), Grib2Error> {
1146    let s = need(s, 19, "section 1", message)?;
1147    Ok((
1148        be_u16(&s[5..7]),
1149        ReferenceTime {
1150            significance: s[11],
1151            year: be_u16(&s[12..14]),
1152            month: s[14],
1153            day: s[15],
1154            hour: s[16],
1155            minute: s[17],
1156            second: s[18],
1157        },
1158    ))
1159}
1160
1161/// Code table 3.2's figure of the Earth, from the template's first 16 bytes after its number.
1162fn read_earth(t: &[u8]) -> Earth {
1163    match t[0] {
1164        0 => Earth::Sphere {
1165            radius_m: 6_367_470.0,
1166        },
1167        1 => Earth::Sphere {
1168            radius_m: scaled(be_u32(&t[2..6]), be_i8(t[1])),
1169        },
1170        6 => Earth::Sphere {
1171            radius_m: 6_371_229.0,
1172        },
1173        8 => Earth::Sphere {
1174            radius_m: 6_371_200.0,
1175        },
1176        code => Earth::Other { code },
1177    }
1178}
1179
1180/// Section 3: the grid.
1181fn read_grid(s: &[u8], message: usize) -> Result<Grid, Grib2Error> {
1182    let s = need(s, 14, "section 3", message)?;
1183    let unsupported = |what, value: u64| Grib2Error::Unsupported {
1184        message,
1185        what,
1186        value,
1187    };
1188    if s[5] != 0 {
1189        return Err(unsupported("source of grid definition", s[5].into()));
1190    }
1191    if s[10] != 0 {
1192        return Err(unsupported("list of points per row, bytes", s[10].into()));
1193    }
1194    let declared = u64::from(be_u32(&s[6..10]));
1195    let template = be_u16(&s[12..14]);
1196    let (ni, nj, flags, scanning, projection, earth) = match template {
1197        0 => {
1198            let s = need(s, 72, "grid template 3.0", message)?;
1199            let earth = read_earth(&s[14..30]);
1200            let basic = be_u32(&s[38..42]);
1201            let subdivisions = be_u32(&s[42..46]);
1202            // Code: 0 or all ones means units of 10⁻⁶ degree.
1203            // Template 3.0's note 1: angles are in units of `basic / subdivisions` degrees, a basic
1204            // angle of 0 or missing meaning 1 and missing subdivisions meaning 10⁶; 0 subdivisions
1205            // are read as missing, as ecCodes reads them.
1206            let basic = if basic == 0 || basic == u32::MAX {
1207                1
1208            } else {
1209                basic
1210            };
1211            let subdivisions = if subdivisions == u32::MAX || subdivisions == 0 {
1212                1_000_000
1213            } else {
1214                subdivisions
1215            };
1216            let degrees = |units: f64| units * f64::from(basic) / f64::from(subdivisions);
1217            let di = be_u32(&s[63..67]);
1218            let dj = be_u32(&s[67..71]);
1219            if di == u32::MAX || dj == u32::MAX || di == 0 || dj == 0 {
1220                return Err(malformed(message, "the grid's increments are missing or 0"));
1221            }
1222            let nj = be_u32(&s[34..38]);
1223            let first_lat_deg = degrees(be_i32(&s[46..50]) as f64);
1224            let (di_deg, dj_deg) = (degrees(f64::from(di)), degrees(f64::from(dj)));
1225            // The rows run from the first latitude by `dj`, north or south; all of them must be on
1226            // the Earth, and a step can't pass a whole turn.
1227            let span_deg = f64::from(nj.saturating_sub(1)) * dj_deg;
1228            let last_lat_deg = if s[71] & 0x40 != 0 {
1229                first_lat_deg + span_deg
1230            } else {
1231                first_lat_deg - span_deg
1232            };
1233            let on_earth = |lat: f64| lat.abs() <= 90.0 + 1e-6;
1234            if !on_earth(first_lat_deg) || !on_earth(last_lat_deg) || di_deg > 360.0 {
1235                return Err(malformed(
1236                    message,
1237                    format!(
1238                        "the grid's rows from {first_lat_deg}° to {last_lat_deg}° or its \
1239                         {di_deg}° steps are off the Earth"
1240                    ),
1241                ));
1242            }
1243            let projection = Projection::LatLon {
1244                first_lat_deg,
1245                first_lon_deg: degrees(be_i32(&s[50..54]) as f64).rem_euclid(360.0),
1246                di_deg,
1247                dj_deg,
1248            };
1249            (
1250                be_u32(&s[30..34]),
1251                be_u32(&s[34..38]),
1252                s[54],
1253                s[71],
1254                projection,
1255                earth,
1256            )
1257        }
1258        30 => {
1259            let s = need(s, 81, "grid template 3.30", message)?;
1260            let earth = read_earth(&s[14..30]);
1261            let Earth::Sphere { radius_m } = earth else {
1262                return Err(unsupported(
1263                    "Lambert grid on the figure of the Earth",
1264                    s[14].into(),
1265                ));
1266            };
1267            // A sphere given by its radius (code 1) must be about the Earth's size.
1268            if !(6.0e6..=7.0e6).contains(&radius_m) {
1269                return Err(malformed(
1270                    message,
1271                    format!("the Earth's radius is {radius_m} m"),
1272                ));
1273            }
1274            let center = s[63];
1275            if center & 0xC0 != 0 {
1276                return Err(unsupported("Lambert projection center flag", center.into()));
1277            }
1278            let micro = |b: &[u8]| be_i32(b) as f64 / 1e6;
1279            let lad = micro(&s[47..51]);
1280            let latin1 = micro(&s[65..69]);
1281            let latin2 = micro(&s[69..73]);
1282            // A secant cone, or grid lengths given away from the tangent latitude, would need the
1283            // scale factor there; no NCEP grid in use has either, so they are refused, not guessed.
1284            if latin1 != latin2 || lad != latin1 || !(latin1 > 0.0 && latin1 < 90.0) {
1285                return Err(malformed(
1286                    message,
1287                    format!(
1288                        "the Lambert grid's Latin1 {latin1}°, Latin2 {latin2}° and LaD {lad}° are \
1289                         not one latitude between 0° and 90°: only a northern tangent cone is read"
1290                    ),
1291                ));
1292            }
1293            let dx = be_u32(&s[55..59]);
1294            let dy = be_u32(&s[59..63]);
1295            if dx == 0 || dy == 0 || dx == u32::MAX || dy == u32::MAX {
1296                return Err(malformed(message, "the grid's lengths are missing or 0"));
1297            }
1298            let projection = Projection::LambertConformal {
1299                first_lat_deg: on_earth(micro(&s[38..42]), message)?,
1300                first_lon_deg: micro(&s[42..46]).rem_euclid(360.0),
1301                tangent_lat_deg: latin1,
1302                orientation_lon_deg: micro(&s[51..55]).rem_euclid(360.0),
1303                dx_m: f64::from(dx) * 1e-3,
1304                dy_m: f64::from(dy) * 1e-3,
1305                radius_m,
1306            };
1307            (
1308                be_u32(&s[30..34]),
1309                be_u32(&s[34..38]),
1310                s[46],
1311                s[64],
1312                projection,
1313                earth,
1314            )
1315        }
1316        other => return Err(unsupported("grid definition template", other.into())),
1317    };
1318    // Scanning mode (flag table 3.4): only bit 2 (+j, 0x40) may be set: +i along rows, rows
1319    // consecutive, not boustrophedon, no offsets.
1320    if scanning & !0x40 != 0 {
1321        return Err(unsupported("scanning mode", scanning.into()));
1322    }
1323    let points = u64::from(ni) * u64::from(nj);
1324    if points == 0 || points != declared {
1325        return Err(malformed(
1326            message,
1327            format!("the grid is {ni} by {nj} but declares {declared} points"),
1328        ));
1329    }
1330    if points > MAX_POINTS {
1331        return Err(unsupported("grid of this many points", points));
1332    }
1333    Ok(Grid {
1334        ni,
1335        nj,
1336        earth,
1337        south_to_north: scanning & 0x40 != 0,
1338        winds_grid_relative: flags & 0x08 != 0,
1339        projection,
1340    })
1341}
1342
1343/// `value / 10^scale`, with one rounding: powers of ten to 10^22 are exact.
1344fn scaled(value: u32, scale: i32) -> f64 {
1345    if scale >= 0 {
1346        f64::from(value) / 10_f64.powi(scale)
1347    } else {
1348        f64::from(value) * 10_f64.powi(-scale)
1349    }
1350}
1351
1352/// A fixed surface from its type, scale factor and scaled value.
1353fn read_surface(s: &[u8]) -> Surface {
1354    let kind = s[0];
1355    let scale = s[1];
1356    let raw = be_u32(&s[2..6]);
1357    let value = (scale != 0xFF && raw != u32::MAX).then(|| scaled(raw, be_i8(scale)));
1358    Surface { kind, value }
1359}
1360
1361/// Section 4: product definition template 4.0, or 4.8 with one time range.
1362fn read_product(s: &[u8], message: usize) -> Result<Product, Grib2Error> {
1363    let s = need(s, 9, "section 4", message)?;
1364    let template = be_u16(&s[7..9]);
1365    let statistics = match template {
1366        0 => None,
1367        8 => {
1368            // Octets 35 to 58: the interval's end, the number of time ranges and the first
1369            // range's statistic, units and length.
1370            let s = need(s, 58, "product template 4.8", message)?;
1371            if s[41] != 1 {
1372                return Err(Grib2Error::Unsupported {
1373                    message,
1374                    what: "number of time ranges",
1375                    value: s[41].into(),
1376                });
1377            }
1378            Some(Statistics {
1379                end: ReferenceTime {
1380                    significance: 2,
1381                    year: be_u16(&s[34..36]),
1382                    month: s[36],
1383                    day: s[37],
1384                    hour: s[38],
1385                    minute: s[39],
1386                    second: s[40],
1387                },
1388                process: s[46],
1389                time_unit: s[48],
1390                length: be_u32(&s[49..53]).into(),
1391            })
1392        }
1393        _ => {
1394            return Err(Grib2Error::Unsupported {
1395                message,
1396                what: "product definition template",
1397                value: template.into(),
1398            });
1399        }
1400    };
1401    let s = need(s, 34, "product template 4.0", message)?;
1402    Ok(Product {
1403        template,
1404        category: s[9],
1405        number: s[10],
1406        process: s[11],
1407        time_unit: s[17],
1408        forecast_time: be_i32(&s[18..22]),
1409        surface: read_surface(&s[22..28]),
1410        second_surface: read_surface(&s[28..34]),
1411        statistics,
1412    })
1413}
1414
1415/// Section 5: data representation template 5.0, 5.2, 5.3 or 5.40.
1416fn read_packing(s: &[u8], message: usize) -> Result<Packing, Grib2Error> {
1417    let s = need(s, 11, "section 5", message)?;
1418    let template = be_u16(&s[9..11]);
1419    match template {
1420        0 => {}
1421        2 | 3 => return complex::read(s, template, message).map(Packing::Complex),
1422        40 => return jpeg2000::read(s, message).map(Packing::Jpeg2000),
1423        _ => {
1424            return Err(Grib2Error::Unsupported {
1425                message,
1426                what: "data representation template",
1427                value: template.into(),
1428            });
1429        }
1430    }
1431    let s = need(s, 21, "data template 5.0", message)?;
1432    let bits = s[19];
1433    if bits > 32 {
1434        return Err(Grib2Error::Unsupported {
1435            message,
1436            what: "bits per value",
1437            value: bits.into(),
1438        });
1439    }
1440    // Table 5.1: 0 floating point, 1 integer. Either unpacks the same way.
1441    if s[20] > 1 {
1442        return Err(Grib2Error::Unsupported {
1443            message,
1444            what: "type of original field values",
1445            value: s[20].into(),
1446        });
1447    }
1448    let reference = f32::from_bits(be_u32(&s[11..15]));
1449    if !reference.is_finite() {
1450        return Err(malformed(message, "the reference value is not finite"));
1451    }
1452    Ok(Packing::Simple(SimplePacking {
1453        reference,
1454        binary_scale: be_i16(&s[15..17]),
1455        decimal_scale: be_i16(&s[17..19]),
1456        bits,
1457        count: be_u32(&s[5..9]),
1458    }))
1459}