gribberish 1.2.0

Parse grib 2 files with Rust
Documentation
use crate::templates::template::{Template, TemplateType};
use crate::utils::{read_u16_from_bytes, read_u32_from_bytes};
use chrono::{DateTime, Utc};

use super::product_template::ProductTemplate;
use super::tables::{FixedSurfaceType, GeneratingProcess, ProbabilityType, TimeUnit};

/// Product Definition Template 4.5
/// Probability forecast at a horizontal level or in a horizontal layer at a point in time
pub struct ProbabilityHorizontalForecastTemplate {
    data: Vec<u8>,
    discipline: u8,
}

impl Template for ProbabilityHorizontalForecastTemplate {
    fn data(&self) -> &[u8] {
        &self.data
    }

    fn template_number(&self) -> u16 {
        5
    }

    fn template_type(&self) -> TemplateType {
        TemplateType::Product
    }

    fn template_name(&self) -> &str {
        "Probability forecast at a horizontal level or in a horizontal layer at a point in time"
    }
}

impl ProbabilityHorizontalForecastTemplate {
    pub fn new(data: Vec<u8>, discipline: u8) -> Self {
        ProbabilityHorizontalForecastTemplate { data, discipline }
    }

    pub fn category_value(&self) -> u8 {
        self.data[9]
    }

    pub fn parameter_value(&self) -> u8 {
        self.data[10]
    }

    pub fn generating_process(&self) -> GeneratingProcess {
        self.data[11].into()
    }

    pub fn observation_cutoff_hours_after_reference_time(&self) -> u16 {
        read_u16_from_bytes(&self.data, 14).unwrap_or(0)
    }

    pub fn observation_cutoff_minutes_after_cutoff_time(&self) -> u8 {
        self.data[16]
    }

    pub fn first_fixed_surface_scale_factor(&self) -> i8 {
        as_signed!(self.data[23], 8, i8)
    }

    pub fn first_fixed_surface_scaled_value(&self) -> i32 {
        as_signed!(read_u32_from_bytes(&self.data, 24).unwrap_or(0), 32, i32)
    }

    pub fn second_fixed_surface_scale_factor(&self) -> i8 {
        as_signed!(self.data[29], 8, i8)
    }

    pub fn second_fixed_surface_scaled_value(&self) -> i32 {
        as_signed!(read_u32_from_bytes(&self.data, 30).unwrap_or(0), 32, i32)
    }

    // Probability-specific fields start at byte 34
    pub fn forecast_probability_number(&self) -> u8 {
        self.data.get(34).copied().unwrap_or(0)
    }

    pub fn total_number_of_forecast_probabilities(&self) -> u8 {
        self.data.get(35).copied().unwrap_or(0)
    }

    pub fn probability_type(&self) -> ProbabilityType {
        self.data.get(36).copied().unwrap_or(0).into()
    }

    pub fn lower_limit_scale_factor(&self) -> i8 {
        as_signed!(self.data.get(37).copied().unwrap_or(0), 8, i8)
    }

    pub fn lower_limit_scaled_value(&self) -> i32 {
        as_signed!(read_u32_from_bytes(&self.data, 38).unwrap_or(0), 32, i32)
    }

    pub fn upper_limit_scale_factor(&self) -> i8 {
        as_signed!(self.data.get(42).copied().unwrap_or(0), 8, i8)
    }

    pub fn upper_limit_scaled_value(&self) -> i32 {
        as_signed!(read_u32_from_bytes(&self.data, 43).unwrap_or(0), 32, i32)
    }

    pub fn array_index(&self) -> Option<usize> {
        match self.first_fixed_surface_type() {
            FixedSurfaceType::OrderedSequence => {
                Some(self.first_fixed_surface_scaled_value() as usize)
            }
            _ => None,
        }
    }

    pub fn scale_value(factor: i8, scaled_value: i32) -> Option<f64> {
        let factor = if factor == i8::MIN + 1 {
            0
        } else {
            factor as i32
        };
        let scale_factor = 10_f64.powi(-factor);

        if scaled_value == i32::MIN + 1 {
            None
        } else {
            Some(scaled_value as f64 * scale_factor)
        }
    }

    pub fn lower_limit(&self) -> Option<f64> {
        Self::scale_value(
            self.lower_limit_scale_factor(),
            self.lower_limit_scaled_value(),
        )
    }

    pub fn upper_limit(&self) -> Option<f64> {
        Self::scale_value(
            self.upper_limit_scale_factor(),
            self.upper_limit_scaled_value(),
        )
    }
}

impl ProductTemplate for ProbabilityHorizontalForecastTemplate {
    fn discipline(&self) -> u8 {
        self.discipline
    }

    fn category_value(&self) -> u8 {
        self.data[9]
    }

    fn parameter_value(&self) -> u8 {
        self.data[10]
    }

    fn generating_process(&self) -> GeneratingProcess {
        self.data[11].into()
    }

    fn time_unit(&self) -> TimeUnit {
        self.data[17].into()
    }

    fn time_increment_unit(&self) -> Option<TimeUnit> {
        None
    }

    fn time_interval(&self) -> u32 {
        read_u32_from_bytes(&self.data, 18).unwrap_or(0)
    }

    fn time_increment_interval(&self) -> Option<u32> {
        None
    }

    fn forecast_datetime(&self, reference_date: DateTime<Utc>) -> DateTime<Utc> {
        let offset_duration = self.time_interval_duration();
        reference_date + offset_duration
    }

    fn forecast_end_datetime(&self, _reference_date: DateTime<Utc>) -> Option<DateTime<Utc>> {
        None
    }

    fn first_fixed_surface_type(&self) -> FixedSurfaceType {
        self.data[22].into()
    }

    fn first_fixed_surface_value(&self) -> Option<f64> {
        ProbabilityHorizontalForecastTemplate::scale_value(
            self.first_fixed_surface_scale_factor(),
            self.first_fixed_surface_scaled_value(),
        )
    }

    fn second_fixed_surface_type(&self) -> FixedSurfaceType {
        self.data[28].into()
    }

    fn second_fixed_surface_value(&self) -> Option<f64> {
        ProbabilityHorizontalForecastTemplate::scale_value(
            self.second_fixed_surface_scale_factor(),
            self.second_fixed_surface_scaled_value(),
        )
    }

    fn derived_forecast_type(&self) -> Option<super::tables::DerivedForecastType> {
        None
    }

    fn statistical_process_type(&self) -> Option<super::tables::TypeOfStatisticalProcessing> {
        None
    }

    fn probability_type(&self) -> Option<super::tables::ProbabilityType> {
        Some(self.data.get(36).copied().unwrap_or(0).into())
    }

    fn forecast_probability_number(&self) -> Option<u8> {
        Some(self.data.get(34).copied().unwrap_or(0))
    }

    fn probability_lower_limit(&self) -> Option<f64> {
        self.lower_limit()
    }

    fn probability_upper_limit(&self) -> Option<f64> {
        self.upper_limit()
    }
}