1use crate::common_parsers::{parse_esa_timestamp, take_n_digits_in_range};
20use crate::{impl_from_str, Mission};
21use chrono::NaiveDateTime;
22use nom::branch::alt;
23use nom::bytes::complete::{tag, tag_no_case, take_while_m_n};
24use nom::character::complete::char;
25use nom::combinator::map;
26use nom::IResult;
27#[cfg(feature = "serde")]
28use serde::{Deserialize, Serialize};
29
30#[derive(PartialOrd, PartialEq, Eq, Debug, Clone, Copy, Hash)]
31#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
32pub enum MissionId {
33 S1A,
34 S1B,
35}
36
37impl From<MissionId> for Mission {
38 fn from(_: MissionId) -> Self {
39 Mission::Sentinel1
40 }
41}
42
43#[derive(PartialOrd, PartialEq, Eq, Debug, Clone, Copy, Hash)]
44#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
45pub enum Mode {
46 IW,
47 EW,
48 WV,
49 S1,
50 S2,
51 S3,
52 S4,
53 S5,
54 S6,
55}
56
57#[derive(PartialOrd, PartialEq, Eq, Debug, Clone, Copy, Hash)]
58#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
59pub enum ProductType {
60 RAW,
61 SLC,
62 GRD,
63 OCN,
64}
65
66#[derive(PartialOrd, PartialEq, Eq, Debug, Clone, Copy, Hash)]
67#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
68pub enum ResolutionClass {
69 Full,
70 High,
71 Medium,
72 NotApplicable,
73}
74
75#[derive(PartialOrd, PartialEq, Eq, Debug, Clone, Copy, Hash)]
76#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
77pub enum ProcessingLevel {
78 Level0,
79 Level1,
80 Level2,
81}
82
83#[derive(PartialOrd, PartialEq, Eq, Debug, Clone, Copy, Hash)]
84#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
85pub enum ProductClass {
86 Standard,
87 Annotation,
88}
89
90#[derive(PartialOrd, PartialEq, Eq, Debug, Clone, Copy, Hash)]
91#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
92pub enum ProductPolarisation {
93 HH,
94 VV,
95 HHHV,
96 VVVH,
97}
98
99#[derive(PartialOrd, PartialEq, Eq, Debug, Clone, Hash)]
103#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
104pub struct Product {
105 pub mission_id: MissionId,
112
113 pub mode: Mode,
120
121 pub product_type: ProductType,
127
128 pub resolution_class: ResolutionClass,
135
136 pub processing_level: ProcessingLevel,
142
143 pub product_class: ProductClass,
150
151 pub polarisation: ProductPolarisation,
161
162 pub start_datetime: NaiveDateTime,
164
165 pub stop_datetime: NaiveDateTime,
167
168 pub orbit_number: u32,
175
176 pub data_take_identifier: String,
182
183 pub product_unique_identifier: String,
190 }
192
193#[derive(PartialOrd, PartialEq, Eq, Debug, Clone, Hash, Copy)]
194#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
195pub enum SwathIdentifier {
196 S1,
197 S2,
198 S3,
199 S4,
200 S5,
201 S6,
202 IW,
203 IW1,
204 IW2,
205 IW3,
206 EW,
207 EW1,
208 EW2,
209 EW3,
210 EW4,
211 EW5,
212 WV,
213 WV1,
214 WV2,
215}
216
217impl SwathIdentifier {
218 pub fn is_s(&self) -> bool {
219 matches!(
220 self,
221 Self::S1 | Self::S2 | Self::S3 | Self::S4 | Self::S5 | Self::S6
222 )
223 }
224
225 pub fn is_iw(&self) -> bool {
226 matches!(self, Self::IW1 | Self::IW2 | Self::IW3 | Self::IW)
227 }
228
229 pub fn is_ew(&self) -> bool {
230 matches!(self, Self::EW1 | Self::EW2 | Self::EW3 | Self::EW)
231 }
232
233 pub fn is_wv(&self) -> bool {
234 matches!(self, Self::WV1 | Self::WV2 | Self::WV)
235 }
236}
237
238#[derive(PartialOrd, PartialEq, Eq, Debug, Clone, Copy, Hash)]
239#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
240pub enum DatasetPolarisation {
241 HH,
242 VV,
243 HV,
244 VH,
245}
246
247#[derive(PartialOrd, PartialEq, Eq, Debug, Clone, Hash)]
251#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
252pub struct Dataset {
253 pub mission_id: MissionId,
260
261 pub swath_identifier: SwathIdentifier,
268
269 pub product_type: ProductType,
275
276 pub polarisation: DatasetPolarisation,
286
287 pub start_datetime: NaiveDateTime,
289
290 pub stop_datetime: NaiveDateTime,
292
293 pub orbit_number: u32,
300
301 pub data_take_identifier: String,
307
308 pub image_number: u32,
315}
316
317fn is_not_product_sep(c: core::primitive::char) -> bool {
318 c != '_'
319}
320
321fn consume_product_sep(s: &str) -> IResult<&str, core::primitive::char> {
322 char('_')(s)
323}
324
325fn consume_dataset_sep(s: &str) -> IResult<&str, core::primitive::char> {
326 char('-')(s)
327}
328
329fn parse_mission_id(s: &str) -> IResult<&str, MissionId> {
330 alt((
331 map(tag_no_case("s1a"), |_| MissionId::S1A),
332 map(tag_no_case("s1b"), |_| MissionId::S1B),
333 ))(s)
334}
335
336fn parse_mode(s: &str) -> IResult<&str, Mode> {
337 alt((
338 map(tag_no_case("iw"), |_| Mode::IW),
339 map(tag_no_case("ew"), |_| Mode::EW),
340 map(tag_no_case("wv"), |_| Mode::WV),
341 map(tag_no_case("s1"), |_| Mode::S1),
342 map(tag_no_case("s2"), |_| Mode::S2),
343 map(tag_no_case("s3"), |_| Mode::S3),
344 map(tag_no_case("s4"), |_| Mode::S4),
345 map(tag_no_case("s5"), |_| Mode::S5),
346 map(tag_no_case("s6"), |_| Mode::S6),
347 ))(s)
348}
349
350fn parse_product_type(s: &str) -> IResult<&str, ProductType> {
351 alt((
352 map(tag_no_case("grd"), |_| ProductType::GRD),
353 map(tag_no_case("ocn"), |_| ProductType::OCN),
354 map(tag_no_case("raw"), |_| ProductType::RAW),
355 map(tag_no_case("slc"), |_| ProductType::SLC),
356 ))(s)
357}
358
359fn parse_resolution(s: &str) -> IResult<&str, ResolutionClass> {
360 alt((
361 map(tag_no_case("f"), |_| ResolutionClass::Full),
362 map(tag_no_case("h"), |_| ResolutionClass::High),
363 map(tag_no_case("m"), |_| ResolutionClass::Medium),
364 map(tag_no_case("_"), |_| ResolutionClass::NotApplicable),
365 ))(s)
366}
367
368fn parse_processing_level(s: &str) -> IResult<&str, ProcessingLevel> {
369 alt((
370 map(tag("0"), |_| ProcessingLevel::Level0),
371 map(tag("1"), |_| ProcessingLevel::Level1),
372 map(tag("2"), |_| ProcessingLevel::Level2),
373 ))(s)
374}
375
376fn parse_product_class(s: &str) -> IResult<&str, ProductClass> {
377 alt((
378 map(tag_no_case("s"), |_| ProductClass::Standard),
379 map(tag_no_case("a"), |_| ProductClass::Annotation),
380 ))(s)
381}
382
383fn parse_product_polarisation(s: &str) -> IResult<&str, ProductPolarisation> {
384 alt((
385 map(tag_no_case("sh"), |_| ProductPolarisation::HH),
386 map(tag_no_case("sv"), |_| ProductPolarisation::VV),
387 map(tag_no_case("dh"), |_| ProductPolarisation::HHHV),
388 map(tag_no_case("dv"), |_| ProductPolarisation::VVVH),
389 ))(s)
390}
391
392pub fn parse_product(s: &str) -> IResult<&str, Product> {
394 let (s, mission_id) = parse_mission_id(s)?;
395 let (s, _) = consume_product_sep(s)?;
396 let (s, mode) = parse_mode(s)?;
397 let (s, _) = consume_product_sep(s)?;
398 let (s, product_type) = parse_product_type(s)?;
399 let (s, resolution_class) = parse_resolution(s)?;
400 let (s, _) = consume_product_sep(s)?;
401 let (s, processing_level) = parse_processing_level(s)?;
402 let (s, product_class) = parse_product_class(s)?;
403 let (s, polarisation) = parse_product_polarisation(s)?;
404 let (s, _) = consume_product_sep(s)?;
405 let (s, start_datetime) = parse_esa_timestamp(s)?;
406 let (s, _) = consume_product_sep(s)?;
407 let (s, stop_datetime) = parse_esa_timestamp(s)?;
408 let (s, _) = consume_product_sep(s)?;
409 let (s, orbit_number) = take_n_digits_in_range(6, 1..=999999)(s)?;
410 let (s, _) = consume_product_sep(s)?;
411 let (s, data_take_identifier) = take_while_m_n(6, 6, is_not_product_sep)(s)?;
412 let (s, _) = consume_product_sep(s)?;
413 let (s, product_unique_identifier) = take_while_m_n(4, 4, is_not_product_sep)(s)?;
414
415 Ok((
416 s,
417 Product {
418 mission_id,
419 mode,
420 product_type,
421 resolution_class,
422 processing_level,
423 product_class,
424 polarisation,
425 start_datetime,
426 stop_datetime,
427 orbit_number,
428 data_take_identifier: data_take_identifier.to_uppercase(),
429 product_unique_identifier: product_unique_identifier.to_uppercase(),
430 },
431 ))
432}
433
434fn parse_dataset_polarisation(s: &str) -> IResult<&str, DatasetPolarisation> {
435 alt((
436 map(tag_no_case("hh"), |_| DatasetPolarisation::HH),
437 map(tag_no_case("vv"), |_| DatasetPolarisation::VV),
438 map(tag_no_case("hv"), |_| DatasetPolarisation::HV),
439 map(tag_no_case("vh"), |_| DatasetPolarisation::VH),
440 ))(s)
441}
442
443fn parse_swath_identifier(s: &str) -> IResult<&str, SwathIdentifier> {
444 alt((
445 map(tag_no_case("s1"), |_| SwathIdentifier::S1),
446 map(tag_no_case("s2"), |_| SwathIdentifier::S2),
447 map(tag_no_case("s3"), |_| SwathIdentifier::S3),
448 map(tag_no_case("s4"), |_| SwathIdentifier::S4),
449 map(tag_no_case("s5"), |_| SwathIdentifier::S5),
450 map(tag_no_case("s6"), |_| SwathIdentifier::S6),
451 map(tag_no_case("iw1"), |_| SwathIdentifier::IW1),
452 map(tag_no_case("iw2"), |_| SwathIdentifier::IW2),
453 map(tag_no_case("iw3"), |_| SwathIdentifier::IW3),
454 map(tag_no_case("iw"), |_| SwathIdentifier::IW),
455 map(tag_no_case("ew1"), |_| SwathIdentifier::EW1),
456 map(tag_no_case("ew2"), |_| SwathIdentifier::EW2),
457 map(tag_no_case("ew3"), |_| SwathIdentifier::EW3),
458 map(tag_no_case("ew4"), |_| SwathIdentifier::EW4),
459 map(tag_no_case("ew5"), |_| SwathIdentifier::EW5),
460 map(tag_no_case("ew"), |_| SwathIdentifier::EW),
461 map(tag_no_case("wv1"), |_| SwathIdentifier::WV1),
462 map(tag_no_case("wv2"), |_| SwathIdentifier::WV2),
463 map(tag_no_case("wv"), |_| SwathIdentifier::WV),
464 ))(s)
465}
466
467pub fn parse_dataset(s: &str) -> IResult<&str, Dataset> {
469 let (s, mission_id) = parse_mission_id(s)?;
470 let (s, _) = consume_dataset_sep(s)?;
471 let (s, swath_identifier) = parse_swath_identifier(s)?;
472 let (s, _) = consume_dataset_sep(s)?;
473 let (s, product_type) = parse_product_type(s)?;
474 let (s, _) = consume_dataset_sep(s)?;
475 let (s, polarisation) = parse_dataset_polarisation(s)?;
476 let (s, _) = consume_dataset_sep(s)?;
477 let (s, start_datetime) = parse_esa_timestamp(s)?;
478 let (s, _) = consume_dataset_sep(s)?;
479 let (s, stop_datetime) = parse_esa_timestamp(s)?;
480 let (s, _) = consume_dataset_sep(s)?;
481 let (s, orbit_number) = take_n_digits_in_range(6, 1..=999999)(s)?;
482 let (s, _) = consume_dataset_sep(s)?;
483 let (s, data_take_identifier) = take_while_m_n(6, 6, is_not_product_sep)(s)?;
484 let (s, _) = consume_dataset_sep(s)?;
485 let (s, image_number) = take_n_digits_in_range(3, 0..=999)(s)?;
486
487 Ok((
488 s,
489 Dataset {
490 mission_id,
491 swath_identifier,
492 product_type,
493 polarisation,
494 start_datetime,
495 stop_datetime,
496 orbit_number,
497 data_take_identifier: data_take_identifier.to_uppercase(),
498 image_number,
499 },
500 ))
501}
502
503impl_from_str!(parse_dataset, Dataset);
504impl_from_str!(parse_product, Product);
505
506#[cfg(test)]
507mod tests {
508 use crate::identifiers::sentinel1::{
509 parse_dataset, parse_product, DatasetPolarisation, MissionId, Mode, ProcessingLevel,
510 ProductClass, ProductPolarisation, ProductType, ResolutionClass, SwathIdentifier,
511 };
512 use crate::identifiers::tests::apply_to_samples_from_txt;
513
514 #[test]
515 fn parse_s1_product() {
516 let (_, product) =
517 parse_product("S1A_IW_GRDH_1SDV_20200207T051836_20200207T051901_031142_039466_A237")
518 .unwrap();
519 assert_eq!(product.mission_id, MissionId::S1A);
520 assert_eq!(product.mode, Mode::IW);
521 assert_eq!(product.product_type, ProductType::GRD);
522 assert_eq!(product.resolution_class, ResolutionClass::High);
523 assert_eq!(product.processing_level, ProcessingLevel::Level1);
524 assert_eq!(product.product_class, ProductClass::Standard);
525 assert_eq!(product.polarisation, ProductPolarisation::VVVH);
526 assert_eq!(product.orbit_number, 31142);
528 assert_eq!(product.data_take_identifier.as_str(), "039466");
529 assert_eq!(product.product_unique_identifier.as_str(), "A237");
530 }
531
532 #[test]
533 fn parse_s1_dataset() {
534 let (_, ds) =
535 parse_dataset("s1a-iw-grd-vh-20221029t171425-20221029t171450-045660-0575ce-002.tiff")
536 .unwrap();
537 assert_eq!(ds.mission_id, MissionId::S1A);
538 assert_eq!(ds.swath_identifier, SwathIdentifier::IW);
539 assert_eq!(ds.product_type, ProductType::GRD);
540 assert_eq!(ds.polarisation, DatasetPolarisation::VH);
541 assert_eq!(ds.orbit_number, 45660);
543 assert_eq!(ds.data_take_identifier.as_str(), "0575CE");
544 }
545
546 #[test]
547 fn parse_s1_dataset_no_fileextension() {
548 let (_, _ds) =
549 parse_dataset("s1a-iw-grd-vh-20221029t171425-20221029t171450-045660-0575ce-002")
550 .unwrap();
551 }
552
553 #[test]
554 fn apply_to_product_testdata() {
555 apply_to_samples_from_txt("sentinel1_products.txt", |s| {
556 parse_product(s).unwrap();
557 })
558 }
559}