use std::{fmt::Display, ops::RangeInclusive};
use geo::Point;
use uom::si::f32::Velocity;
use crate::{DiprError, ParseResult, inch_per_hour, utils::*};
#[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Hash)]
pub enum OperationalMode {
Maintenance,
CleanAir,
Precipitation,
}
impl Display for OperationalMode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
OperationalMode::CleanAir => write!(f, "Clean Air"),
OperationalMode::Maintenance => write!(f, "Maintenance"),
OperationalMode::Precipitation => write!(f, "Precipitation"),
}
}
}
impl TryFrom<i16> for OperationalMode {
type Error = DiprError;
fn try_from(value: i16) -> Result<Self, Self::Error> {
match value {
0 => Ok(OperationalMode::Maintenance),
1 => Ok(OperationalMode::CleanAir),
2 => Ok(OperationalMode::Precipitation),
v => Err(DiprError::ValueOutOfRange(format!(
"operational mode: got {v}, expected {:?}",
ProductDescription::OPERATIONAL_MODE_RANGE
))),
}
}
}
pub(crate) struct ProductDescription {
pub(crate) location: Point<f32>,
pub(crate) operational_mode: OperationalMode,
pub(crate) precip_detected: bool,
pub(crate) max_precip_rate: Velocity,
pub(crate) uncompressed_size: u32,
}
impl ProductDescription {
const NAME: &'static str = "product description block";
const BLOCK_DIVIDER_VALUE: i16 = -1;
const LATITUDE_RANGE: RangeInclusive<i32> = -90_000..=90_000;
const LONGITUDE_RANGE: RangeInclusive<i32> = -180_000..=180_000;
const OPERATIONAL_MODE_RANGE: RangeInclusive<i16> = 0..=2;
const PRECIP_DETECTED_RANGE: RangeInclusive<i8> = 0..=1;
}
pub(crate) fn product_description(input: &[u8]) -> ParseResult<ProductDescription> {
let (block_divider, tail) = take_i16(input)?;
check_value(
ProductDescription::BLOCK_DIVIDER_VALUE,
block_divider,
"block divider",
ProductDescription::NAME,
)?;
let (latitude_int, tail) = take_i32(tail)?;
check_range_inclusive(
ProductDescription::LATITUDE_RANGE,
latitude_int,
"latitude",
ProductDescription::NAME,
)?;
let (longitude_int, tail) = take_i32(tail)?;
check_range_inclusive(
ProductDescription::LONGITUDE_RANGE,
longitude_int,
"longitude",
ProductDescription::NAME,
)?;
let (_, tail) = take_bytes(tail, 4)?;
let (operational_mode_int, tail) = take_i16(tail)?;
check_range_inclusive(
ProductDescription::OPERATIONAL_MODE_RANGE,
operational_mode_int,
"operational mode",
ProductDescription::NAME,
)?;
let (_, tail) = take_bytes(tail, 24)?;
let (precip_detected_int, tail) = take_i8(tail)?;
check_range_inclusive(
ProductDescription::PRECIP_DETECTED_RANGE,
precip_detected_int,
"precipitation detected",
ProductDescription::NAME,
)?;
let (_, tail) = take_bytes(tail, 33)?;
let (max_precip_rate, tail) = take_i16(tail)?;
let (_, tail) = take_bytes(tail, 8)?;
let (uncompressed_size, tail) = take_i32(tail)?;
let (_, tail) = take_bytes(tail, 14)?;
let location = Point::new(longitude_int as f32 / 1000., latitude_int as f32 / 1000.);
let operational_mode = operational_mode_int.try_into()?;
let precip_detected = precip_detected_int != 0;
let max_precip_rate = Velocity::new::<inch_per_hour>(max_precip_rate as f32 / 1000.);
let uncompressed_size = uncompressed_size as u32;
Ok((
ProductDescription {
location,
operational_mode,
precip_detected,
max_precip_rate,
uncompressed_size,
},
tail,
))
}