eo_identifiers/identifiers/
sentinel3.rs

1//! Sentinel 3
2//!
3//! [naming convention](https://sentinel.esa.int/web/sentinel/user-guides/sentinel-3-olci/naming-convention)
4//!
5//! # Example
6//!
7//! ```rust
8//! use eo_identifiers::identifiers::sentinel3::Product;
9//! use std::str::FromStr;
10//!
11//! assert!(
12//!     Product::from_str("S3A_OL_1_EFR____20220801T210143_20220801T210443_20220803T023357_0179_088_157_1800_MAR_O_NT_002")
13//!     .is_ok()
14//! );
15//! ```
16
17use crate::common_parsers::{
18    is_char_alphanumeric, parse_esa_timestamp, take_alphanumeric_n, take_n_digits,
19};
20use crate::{impl_from_str, Mission};
21use chrono::NaiveDateTime;
22use nom::branch::alt;
23use nom::bytes::complete::{tag_no_case, take, take_while_m_n};
24use nom::character::complete::char;
25use nom::combinator::map;
26use nom::sequence::tuple;
27use nom::IResult;
28#[cfg(feature = "serde")]
29use serde::{Deserialize, Serialize};
30
31#[derive(PartialOrd, PartialEq, Eq, Debug, Clone, Copy, Hash)]
32#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
33pub enum MissionId {
34    S3A,
35    S3B,
36    S3AB,
37}
38
39impl From<MissionId> for Mission {
40    fn from(_: MissionId) -> Self {
41        Mission::Sentinel3
42    }
43}
44
45#[derive(PartialOrd, PartialEq, Eq, Debug, Clone, Copy, Hash)]
46#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
47pub enum DataSource {
48    OLCI,
49    SLSTR,
50    Synergy,
51    SRAL,
52    DORIS,
53    MWR,
54    GNSS,
55}
56
57#[allow(non_camel_case_types)]
58#[derive(PartialOrd, PartialEq, Eq, Debug, Clone, Hash)]
59#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
60pub enum DataType {
61    AER_AX,
62    AOD,
63    ATP_AX,
64    CAL,
65    CR0,
66    CR1,
67    EFR,
68    EFR_BW,
69    ERR,
70    ERR_BW,
71    FRP,
72    INS_AX,
73    LAN,
74    LAP_AX,
75    LFR,
76    LFR_BW,
77    LRR,
78    LRR_BW,
79    LST,
80    LST_BW,
81    LVI_AX,
82    MSIR,
83    RAC,
84    RBT,
85    RBT_BW,
86    SLT,
87    SPC,
88    SRA,
89    SYN,
90    SYN_BW,
91    V10,
92    V10_BW,
93    VG1,
94    VG1_BW,
95    VGP,
96    VGP_BW,
97    WAT,
98    WCT,
99    WFR,
100    WFR_BW,
101    WRR,
102    WRR_BW,
103    WST,
104    WST_BW,
105    Other(String),
106}
107
108#[derive(PartialOrd, PartialEq, Eq, Debug, Clone, Hash)]
109#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
110pub enum InstanceId {
111    Stripe {
112        duration: u32,
113        cycle_number: u32,
114        relative_order_number: u32,
115    },
116    Frame {
117        duration: u32,
118        cycle_number: u32,
119        relative_order_number: u32,
120        frame_along_track_coordinate: u32,
121    },
122    GlobalTile,
123    Tile {
124        tile_identifier: String,
125    },
126    Aux,
127}
128
129/// Sentinel 3 product
130#[derive(PartialOrd, PartialEq, Eq, Debug, Clone, Hash)]
131#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
132pub struct Product {
133    /// mission id
134    pub mission_id: MissionId,
135
136    pub data_source: DataSource,
137
138    pub processing_level: Option<u8>,
139
140    pub data_type: DataType,
141
142    pub start_datetime: NaiveDateTime,
143    pub stop_datetime: NaiveDateTime,
144    pub product_creation_datetime: NaiveDateTime,
145    pub instance_id: InstanceId,
146    pub centre_generating_file: String,
147    pub platform: Option<Platform>,
148    pub timeliness: Option<Timeliness>,
149
150    /// baseline collection or data usage
151    pub collection_or_usage: Option<String>,
152}
153
154#[derive(PartialOrd, PartialEq, Eq, Debug, Clone, Copy, Hash)]
155#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
156pub enum Platform {
157    Operational,
158    Reference,
159    Development,
160    Reprocessing,
161}
162
163#[derive(PartialOrd, PartialEq, Eq, Debug, Clone, Copy, Hash)]
164#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
165pub enum Timeliness {
166    NRT,
167    STC,
168    NTC,
169}
170
171fn consume_product_sep(s: &str) -> IResult<&str, core::primitive::char> {
172    char('_')(s)
173}
174
175fn parse_mission_id(s: &str) -> IResult<&str, MissionId> {
176    alt((
177        map(tag_no_case("s3a"), |_| MissionId::S3A),
178        map(tag_no_case("s3b"), |_| MissionId::S3B),
179        map(tag_no_case("s3_"), |_| MissionId::S3AB),
180    ))(s)
181}
182
183fn parse_data_source(s: &str) -> IResult<&str, DataSource> {
184    alt((
185        map(tag_no_case("ol"), |_| DataSource::OLCI),
186        map(tag_no_case("sl"), |_| DataSource::SLSTR),
187        map(tag_no_case("sy"), |_| DataSource::Synergy),
188        map(tag_no_case("sr"), |_| DataSource::SRAL),
189        map(tag_no_case("do"), |_| DataSource::DORIS),
190        map(tag_no_case("mw"), |_| DataSource::MWR),
191        map(tag_no_case("gn"), |_| DataSource::GNSS),
192    ))(s)
193}
194
195fn parse_data_type(s: &str) -> IResult<&str, DataType> {
196    alt((
197        alt((
198            map(tag_no_case("AER_AX"), |_| DataType::AER_AX),
199            map(tag_no_case("AOD___"), |_| DataType::AOD),
200            map(tag_no_case("ATP_AX"), |_| DataType::ATP_AX),
201            map(tag_no_case("CAL___"), |_| DataType::CAL),
202            map(tag_no_case("CR0___"), |_| DataType::CR0),
203            map(tag_no_case("CR1___"), |_| DataType::CR1),
204            map(tag_no_case("EFR___"), |_| DataType::EFR),
205            map(tag_no_case("EFR_BW"), |_| DataType::EFR_BW),
206        )),
207        alt((
208            map(tag_no_case("ERR___"), |_| DataType::ERR),
209            map(tag_no_case("ERR_BW"), |_| DataType::ERR_BW),
210            map(tag_no_case("FRP___"), |_| DataType::FRP),
211            map(tag_no_case("INS_AX"), |_| DataType::INS_AX),
212            map(tag_no_case("LAN___"), |_| DataType::LAN),
213            map(tag_no_case("LAP_AX"), |_| DataType::LAP_AX),
214            map(tag_no_case("LFR___"), |_| DataType::LFR),
215            map(tag_no_case("LFR_BW"), |_| DataType::LFR_BW),
216            map(tag_no_case("LRR___"), |_| DataType::LRR),
217            map(tag_no_case("LRR_BW"), |_| DataType::LRR_BW),
218            map(tag_no_case("LST___"), |_| DataType::LST),
219            map(tag_no_case("LST_BW"), |_| DataType::LST_BW),
220        )),
221        alt((
222            map(tag_no_case("LVI_AX"), |_| DataType::LVI_AX),
223            map(tag_no_case("MSIR__"), |_| DataType::MSIR),
224            map(tag_no_case("RAC___"), |_| DataType::RAC),
225            map(tag_no_case("RBT___"), |_| DataType::RBT),
226            map(tag_no_case("RBT_BW"), |_| DataType::RBT_BW),
227            map(tag_no_case("SLT___"), |_| DataType::SLT),
228            map(tag_no_case("SPC___"), |_| DataType::SPC),
229            map(tag_no_case("SRA___"), |_| DataType::SRA),
230            map(tag_no_case("SYN___"), |_| DataType::SYN),
231            map(tag_no_case("SYN_BW"), |_| DataType::SYN_BW),
232            map(tag_no_case("V10___"), |_| DataType::V10),
233        )),
234        alt((
235            map(tag_no_case("V10_BW"), |_| DataType::V10_BW),
236            map(tag_no_case("VG1___"), |_| DataType::VG1),
237            map(tag_no_case("VG1_BW"), |_| DataType::VG1_BW),
238            map(tag_no_case("VGP___"), |_| DataType::VGP),
239            map(tag_no_case("VGP_BW"), |_| DataType::VGP_BW),
240            map(tag_no_case("WAT___"), |_| DataType::WAT),
241            map(tag_no_case("WCT___"), |_| DataType::WCT),
242            map(tag_no_case("WFR___"), |_| DataType::WFR),
243            map(tag_no_case("WFR_BW"), |_| DataType::WFR_BW),
244            map(tag_no_case("WRR___"), |_| DataType::WRR),
245            map(tag_no_case("WRR_BW"), |_| DataType::WRR_BW),
246            map(tag_no_case("WST___"), |_| DataType::WST),
247            map(tag_no_case("WST_BW"), |_| DataType::WST_BW),
248            map(take(6usize), |v: &str| {
249                DataType::Other(v.trim_end_matches('_').to_uppercase())
250            }),
251        )),
252    ))(s)
253}
254
255fn parse_instance(s: &str) -> IResult<&str, InstanceId> {
256    alt((
257        map(take_while_m_n(17, 17, |c| c == '_'), |_| InstanceId::Aux),
258        map(tag_no_case("GLOBAL___________"), |_| InstanceId::GlobalTile),
259        map(
260            tuple((
261                take_n_digits::<u32>(4),
262                consume_product_sep,
263                take_n_digits::<u32>(3),
264                consume_product_sep,
265                take_n_digits::<u32>(3),
266                consume_product_sep,
267                take_while_m_n(4, 4, |c| c == '_'),
268            )),
269            |(duration, _, cycle_number, _, relative_order_number, _, _)| InstanceId::Stripe {
270                duration,
271                cycle_number,
272                relative_order_number,
273            },
274        ),
275        map(
276            tuple((
277                take_n_digits::<u32>(4),
278                consume_product_sep,
279                take_n_digits::<u32>(3),
280                consume_product_sep,
281                take_n_digits::<u32>(3),
282                consume_product_sep,
283                take_n_digits::<u32>(4),
284            )),
285            |(
286                duration,
287                _,
288                cycle_number,
289                _,
290                relative_order_number,
291                _,
292                frame_along_track_coordinate,
293            )| InstanceId::Frame {
294                duration,
295                cycle_number,
296                relative_order_number,
297                frame_along_track_coordinate,
298            },
299        ),
300        map(take_alphanumeric_n(17), |ti| InstanceId::Tile {
301            tile_identifier: ti.to_uppercase(),
302        }),
303    ))(s)
304}
305
306fn parse_platform(s: &str) -> IResult<&str, Option<Platform>> {
307    alt((
308        map(tag_no_case("o"), |_| Some(Platform::Operational)),
309        map(tag_no_case("f"), |_| Some(Platform::Reference)),
310        map(tag_no_case("d"), |_| Some(Platform::Development)),
311        map(tag_no_case("r"), |_| Some(Platform::Reprocessing)),
312        map(consume_product_sep, |_| None),
313    ))(s)
314}
315
316fn parse_timeliness(s: &str) -> IResult<&str, Option<Timeliness>> {
317    alt((
318        map(tag_no_case("nr"), |_| Some(Timeliness::NRT)),
319        map(tag_no_case("st"), |_| Some(Timeliness::STC)),
320        map(tag_no_case("nt"), |_| Some(Timeliness::NTC)),
321        map(tuple((consume_product_sep, consume_product_sep)), |_| None),
322    ))(s)
323}
324
325/// nom parser function
326pub fn parse_product(s: &str) -> IResult<&str, Product> {
327    let (s, mission_id) = parse_mission_id(s)?;
328    let (s, _) = consume_product_sep(s)?;
329    let (s, data_source) = parse_data_source(s)?;
330    let (s, _) = consume_product_sep(s)?;
331    let (s, processing_level) = alt((
332        map(take_n_digits::<u8>(1), Some),
333        map(consume_product_sep, |_| None),
334    ))(s)?;
335    let (s, _) = consume_product_sep(s)?;
336    let (s, data_type) = parse_data_type(s)?;
337    let (s, _) = consume_product_sep(s)?;
338    let (s, start_datetime) = parse_esa_timestamp(s)?;
339    let (s, _) = consume_product_sep(s)?;
340    let (s, stop_datetime) = parse_esa_timestamp(s)?;
341    let (s, _) = consume_product_sep(s)?;
342    let (s, product_creation_datetime) = parse_esa_timestamp(s)?;
343    let (s, _) = consume_product_sep(s)?;
344    let (s, instance_id) = parse_instance(s)?;
345    let (s, _) = consume_product_sep(s)?;
346    let (s, centre_generating_file) = map(take_alphanumeric_n(3), |v| v.to_uppercase())(s)?;
347    let (s, _) = consume_product_sep(s)?;
348    let (s, platform) = parse_platform(s)?;
349    let (s, _) = consume_product_sep(s)?;
350    let (s, timeliness) = parse_timeliness(s)?;
351    let (s, _) = consume_product_sep(s)?;
352    let (s, collection_or_usage) = alt((
353        map(take_while_m_n(1, 3, is_char_alphanumeric), |d: &str| {
354            Some(d.to_uppercase())
355        }),
356        map(take_while_m_n(3, 3, |c| c == '_'), |_| None),
357    ))(s)?;
358
359    Ok((
360        s,
361        Product {
362            mission_id,
363            data_source,
364            processing_level,
365            data_type,
366            start_datetime,
367            stop_datetime,
368            product_creation_datetime,
369            instance_id,
370            centre_generating_file,
371            platform,
372            timeliness,
373            collection_or_usage,
374        },
375    ))
376}
377
378impl_from_str!(parse_product, Product);
379
380#[cfg(test)]
381mod tests {
382    use crate::identifiers::sentinel3::parse_product;
383    use crate::identifiers::tests::apply_to_samples_from_txt;
384
385    #[test]
386    fn apply_to_product_testdata() {
387        apply_to_samples_from_txt("sentinel3_products.txt", |s| {
388            parse_product(s).unwrap();
389        })
390    }
391}