1use 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#[derive(PartialOrd, PartialEq, Eq, Debug, Clone, Hash)]
131#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
132pub struct Product {
133 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 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
325pub 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}