grib 0.17.0

GRIB format parser & writer for Rust
Documentation
use crate::def::grib2::template::param_set;

/// An auxiliary struct for creating the definition of a latitude/longitude grid
/// ([`def::grib2::template::param_set::LatLonGrid`](`crate::def::grib2::template::param_set::LatLonGrid`))
/// in a more intuitive way.
///
/// You can specify any value within the range of -360 degrees to 360 degrees
/// for the longitude values of the first and last points. However, to
/// distinguish the grid area, set the longitude value at the western end of the
/// grid to a value smaller than that at the eastern end.
///
/// For example:
///
/// - For a grid extending eastward from 0° to 5°W, specify the first point’s
///   longitude as 0.0 and the last point’s longitude as 355.0.
/// - For a grid extending westward from 0° to 5°W, specify the first point’s
///   longitude as 0.0 and the last point’s longitude as -5.0.
/// - For a grid extending eastward from 180° to 175°W, specify the first
///   point’s longitude as -180.0 and the last point’s longitude as 175.0.
/// - For a grid extending westward from 180° to 175°W, specify the first
///   point’s longitude as 180.0 and the last point’s longitude as 175.0.
///
/// Longitude values are automatically normalized during conversion to
/// [`def::grib2::template::param_set::LatLonGrid`](`crate::def::grib2::template::param_set::LatLonGrid`)
/// to comply with GRIB2 specification.
pub struct LatLonGrid {
    /// Shape of the grid, in the form `(ni, nj)`.
    pub shape: (usize, usize),
    /// First coordinate point, in the form `(lat, lon)`. The order in which you
    /// specify the "first" point should correspond to the order of the array of
    /// grid point values.
    pub first_point: (f64, f64),
    /// The coordinate point diagonally opposite the first coordinate point, in
    /// the form `(lat, lon)`.
    pub last_point: (f64, f64),
    /// Whether the grid point values consecutive in the `i`-direction
    /// (longitude direction) or not.
    pub i_consecutive: bool,
}

impl LatLonGrid {
    /// Returns the number of points of the grid.
    pub fn num_points(&self) -> usize {
        self.shape.0 * self.shape.1
    }

    /// Returns the scanning mode of the grid.
    pub fn scanning_mode(&self) -> param_set::ScanningMode {
        let mut scan_mode: u8 = 0b00000000;
        if self.first_point.1 > self.last_point.1 {
            scan_mode |= 0b10000000;
        }
        if self.first_point.0 < self.last_point.0 {
            scan_mode |= 0b01000000;
        }
        if !self.i_consecutive {
            scan_mode |= 0b00100000;
        }
        param_set::ScanningMode(scan_mode)
    }
}

impl From<&LatLonGrid> for param_set::LatLonGrid {
    fn from(value: &LatLonGrid) -> Self {
        let (ni, nj) = (value.shape.0 as u32, value.shape.1 as u32);

        let (first_point_lat, first_point_lon) = (
            lat_in_microdegrees(value.first_point.0),
            lon_in_microdegrees(value.first_point.1),
        );
        let (last_point_lat, last_point_lon) = (
            lat_in_microdegrees(value.last_point.0),
            lon_in_microdegrees(value.last_point.1),
        );

        let i_direction_inc =
            inc_in_microdegrees(value.first_point.1, value.last_point.1, value.shape.0 - 1);
        let j_direction_inc =
            inc_in_microdegrees(value.first_point.0, value.last_point.0, value.shape.1 - 1);
        let scanning_mode = value.scanning_mode();

        param_set::LatLonGrid {
            grid: param_set::Grid {
                ni,
                nj,
                initial_production_domain_basic_angle: 0,
                basic_angle_subdivisions: 0xffffffff,
                first_point_lat,
                first_point_lon,
                resolution_and_component_flags: param_set::ResolutionAndComponentFlags(0b00110000),
                last_point_lat,
                last_point_lon,
            },
            i_direction_inc,
            j_direction_inc,
            scanning_mode,
        }
    }
}

fn lat_in_microdegrees(val_in_degrees: f64) -> i32 {
    microdegrees(val_in_degrees)
}

fn lon_in_microdegrees(val_in_degrees: f64) -> u32 {
    microdegrees(normalized_longitude(val_in_degrees)) as u32
}

fn inc_in_microdegrees(first: f64, last: f64, n_spacing: usize) -> u32 {
    let diff = if first < last {
        last - first
    } else {
        first - last
    };
    microdegrees(diff) as u32 / n_spacing as u32
}

fn microdegrees(val_in_degrees: f64) -> i32 {
    (val_in_degrees * 1e6) as i32
}

fn normalized_longitude(val: f64) -> f64 {
    if val < 0. { val + 360. } else { val }
}

#[cfg(test)]
mod tests {
    use super::*;

    macro_rules! test_lat_lon_grid_definition {
        ($(($name:ident, $input:expr, $expected:expr),)*) => ($(
            #[test]
            fn $name() {
                let input = $input;
                let params = crate::def::grib2::template::param_set::LatLonGrid::from(&input);
                let actual = (
                    (params.grid.ni, params.grid.nj),
                    (params.grid.first_point_lat, params.grid.first_point_lon),
                    (params.grid.last_point_lat, params.grid.last_point_lon),
                    (params.i_direction_inc, params.j_direction_inc),
                    params.scanning_mode,
                );
                assert_eq!(actual, $expected);
            }
        )*);
    }

    test_lat_lon_grid_definition! {
        (
            lat_lon_grid_definition_for_neg_lat_inc_and_pos_lon_inc,
            LatLonGrid {
                shape: (201, 101),
                first_point: (40., 130.),
                last_point: (30., 140.),
                i_consecutive: true,
            },
            (
                (201, 101),
                (40000000, 130000000),
                (30000000, 140000000),
                (50000, 100000),
                param_set::ScanningMode(0b00000000),
            )
        ),
        (
            lat_lon_grid_definition_for_neg_lat_inc_and_neg_lon_inc,
            LatLonGrid {
                shape: (201, 101),
                first_point: (40., 140.),
                last_point: (30., 130.),
                i_consecutive: true,
            },
            (
                (201, 101),
                (40000000, 140000000),
                (30000000, 130000000),
                (50000, 100000),
                param_set::ScanningMode(0b10000000),
            )
        ),
        (
            lat_lon_grid_definition_for_pos_lat_inc_and_pos_lon_inc,
            LatLonGrid {
                shape: (201, 101),
                first_point: (30., 130.),
                last_point: (40., 140.),
                i_consecutive: true,
            },
            (
                (201, 101),
                (30000000, 130000000),
                (40000000, 140000000),
                (50000, 100000),
                param_set::ScanningMode(0b01000000),
            )
        ),
        (
            lat_lon_grid_definition_for_pos_lat_inc_and_neg_lon_inc,
            LatLonGrid {
                shape: (201, 101),
                first_point: (30., 140.),
                last_point: (40., 130.),
                i_consecutive: true,
            },
            (
                (201, 101),
                (30000000, 140000000),
                (40000000, 130000000),
                (50000, 100000),
                param_set::ScanningMode(0b11000000),
            )
        ),
        (
            lat_lon_grid_definition_for_eastward_from_0_deg,
            LatLonGrid {
                shape: (2, 2),
                first_point: (40., 0.),
                last_point: (30., 355.),
                i_consecutive: true,
            },
            (
                (2, 2),
                (40000000, 0),
                (30000000, 355000000),
                (355000000, 10000000),
                param_set::ScanningMode(0b00000000),
            )
        ),
        (
            lat_lon_grid_definition_for_westward_from_0_deg,
            LatLonGrid {
                shape: (2, 2),
                first_point: (40., 0.),
                last_point: (30., -5.),
                i_consecutive: true,
            },
            (
                (2, 2),
                (40000000, 0),
                (30000000, 355000000),
                (5000000, 10000000),
                param_set::ScanningMode(0b10000000),
            )
        ),
        (
            lat_lon_grid_definition_for_eastward_from_180_deg,
            LatLonGrid {
                shape: (2, 2),
                first_point: (40., -180.),
                last_point: (30., 175.),
                i_consecutive: true,
            },
            (
                (2, 2),
                (40000000, 180000000),
                (30000000, 175000000),
                (355000000, 10000000),
                param_set::ScanningMode(0b00000000),
            )
        ),
        (
            lat_lon_grid_definition_for_westward_from_180_deg,
            LatLonGrid {
                shape: (2, 2),
                first_point: (40., 180.),
                last_point: (30., 175.),
                i_consecutive: true,
            },
            (
                (2, 2),
                (40000000, 180000000),
                (30000000, 175000000),
                (5000000, 10000000),
                param_set::ScanningMode(0b10000000),
            )
        ),
    }

    #[test]
    fn normalized_longitude_value() {
        assert_eq!(normalized_longitude(-360.), 0.);
        assert_eq!(normalized_longitude(-180.), 180.);
        assert_eq!(normalized_longitude(0.), 0.);
        assert_eq!(normalized_longitude(180.), 180.);
        assert_eq!(normalized_longitude(360.), 360.);
    }
}