use chrono::NaiveDateTime;
use nom::branch::alt;
use nom::bytes::complete::tag_no_case;
use nom::character::complete::char;
use nom::combinator::map;
use nom::IResult;
use crate::common_parsers::{parse_esa_timestamp, take_alphanumeric_n, take_n_digits_in_range};
use crate::{impl_from_str, Mission};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[derive(PartialOrd, PartialEq, Eq, Debug, Clone, Copy, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum MissionId {
S2A,
S2B,
}
impl From<MissionId> for Mission {
fn from(_: MissionId) -> Self {
Mission::Sentinel2
}
}
#[derive(PartialOrd, PartialEq, Eq, Debug, Clone, Copy, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum ProductLevel {
L1C,
L2A,
}
#[derive(PartialOrd, PartialEq, Eq, Debug, Clone, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Product {
pub mission_id: MissionId,
pub product_level: ProductLevel,
pub start_datetime: NaiveDateTime,
pub pdgs_baseline_number: (u8, u8),
pub relative_orbit_number: u8,
pub tile_number: String,
pub product_discriminator: String,
}
fn consume_product_sep(s: &str) -> IResult<&str, core::primitive::char> {
char('_')(s)
}
fn parse_mission_id(s: &str) -> IResult<&str, MissionId> {
alt((
map(tag_no_case("s2a"), |_| MissionId::S2A),
map(tag_no_case("s2b"), |_| MissionId::S2B),
))(s)
}
fn parse_product_level(s: &str) -> IResult<&str, ProductLevel> {
alt((
map(tag_no_case("l1c"), |_| ProductLevel::L1C),
map(tag_no_case("l2a"), |_| ProductLevel::L2A),
))(s)
}
fn parse_processing_baseline_number(s: &str) -> IResult<&str, (u8, u8)> {
let (s, _) = tag_no_case("n")(s)?;
let (s, x) = take_n_digits_in_range(2, 0..=99)(s)?;
let (s, y) = take_n_digits_in_range(2, 0..=99)(s)?;
Ok((s, (x, y)))
}
fn parse_relative_orbit_number(s: &str) -> IResult<&str, u8> {
let (s, _) = tag_no_case("r")(s)?;
let (s, ron) = take_n_digits_in_range(3, 1..=143)(s)?;
Ok((s, ron))
}
fn parse_tile_number(s: &str) -> IResult<&str, String> {
let (s, _) = tag_no_case("t")(s)?;
let (s, tn) = take_alphanumeric_n(5)(s)?;
Ok((s, tn.to_uppercase()))
}
pub fn parse_product(s: &str) -> IResult<&str, Product> {
let (s, mission_id) = parse_mission_id(s)?;
let (s, _) = consume_product_sep(s)?;
let (s, _) = tag_no_case("msi")(s)?;
let (s, product_level) = parse_product_level(s)?;
let (s, _) = consume_product_sep(s)?;
let (s, start_datetime) = parse_esa_timestamp(s)?;
let (s, _) = consume_product_sep(s)?;
let (s, pdgs_baseline_number) = parse_processing_baseline_number(s)?;
let (s, _) = consume_product_sep(s)?;
let (s, relative_orbit_number) = parse_relative_orbit_number(s)?;
let (s, _) = consume_product_sep(s)?;
let (s, tile_number) = parse_tile_number(s)?;
let (s, _) = consume_product_sep(s)?;
let (s, product_discriminator) = take_alphanumeric_n(15)(s)?;
Ok((
s,
Product {
mission_id,
product_level,
start_datetime,
pdgs_baseline_number,
relative_orbit_number,
tile_number,
product_discriminator: product_discriminator.to_uppercase(),
},
))
}
impl_from_str!(parse_product, Product);
#[cfg(test)]
mod tests {
use crate::identifiers::sentinel2::{parse_product, MissionId, Product, ProductLevel};
use crate::identifiers::tests::apply_to_samples_from_txt;
use std::str::FromStr;
#[test]
fn parse_s2_product() {
let (_, product) =
parse_product("S2A_MSIL1C_20170105T013442_N0204_R031_T53NMJ_20170105T013443.SAFE")
.unwrap();
assert_eq!(product.mission_id, MissionId::S2A);
assert_eq!(product.product_level, ProductLevel::L1C);
assert_eq!(product.pdgs_baseline_number, (2, 4));
assert_eq!(product.relative_orbit_number, 31);
assert_eq!(product.tile_number.as_str(), "53NMJ");
assert_eq!(product.product_discriminator.as_str(), "20170105T013443");
}
#[test]
fn apply_to_product_testdata() {
apply_to_samples_from_txt("sentinel2_products.txt", |s| {
parse_product(s).unwrap();
})
}
#[test]
fn test_from_str() {
assert!(
Product::from_str("S2A_MSIL1C_20170105T013442_N0204_R031_T53NMJ_20170105T013443")
.is_ok()
);
}
}