1use crate::common_parsers::{
19 date_year, parse_simple_date, take_alphanumeric, take_alphanumeric_n, take_n_digits,
20 take_n_digits_in_range,
21};
22use crate::{impl_from_str, Mission, Name, NameLong};
23use chrono::{Duration, NaiveDate};
24use nom::branch::alt;
25use nom::bytes::complete::{tag, tag_no_case, take};
26use nom::combinator::{map, opt};
27use nom::error::ErrorKind;
28use nom::sequence::tuple;
29use nom::IResult;
30#[cfg(feature = "serde")]
31use serde::{Deserialize, Serialize};
32
33#[derive(PartialOrd, PartialEq, Eq, Debug, Clone, Copy, Hash)]
34#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
35pub enum MissionId {
36 Landsat1,
37 Landsat2,
38 Landsat3,
39 Landsat4,
40 Landsat5,
41 Landsat6,
42 Landsat7,
43 Landsat8,
44 Landsat9,
45}
46
47impl From<u8> for MissionId {
48 fn from(v: u8) -> Self {
49 match v {
50 1 => Self::Landsat1,
51 2 => Self::Landsat2,
52 3 => Self::Landsat3,
53 4 => Self::Landsat4,
54 5 => Self::Landsat5,
55 6 => Self::Landsat6,
56 7 => Self::Landsat7,
57 8 => Self::Landsat8,
58 9 => Self::Landsat9,
59 _ => panic!("invalid landsat satellite number"),
60 }
61 }
62}
63
64impl From<MissionId> for Mission {
65 fn from(mission: MissionId) -> Self {
66 match mission {
67 MissionId::Landsat1 => Self::Landsat1,
68 MissionId::Landsat2 => Self::Landsat2,
69 MissionId::Landsat3 => Self::Landsat3,
70 MissionId::Landsat4 => Self::Landsat4,
71 MissionId::Landsat5 => Self::Landsat5,
72 MissionId::Landsat6 => Self::Landsat6,
73 MissionId::Landsat7 => Self::Landsat7,
74 MissionId::Landsat8 => Self::Landsat8,
75 MissionId::Landsat9 => Self::Landsat9,
76 }
77 }
78}
79
80#[allow(non_camel_case_types)]
81#[derive(PartialOrd, PartialEq, Eq, Debug, Clone, Copy, Hash)]
82#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
83pub enum Sensor {
84 OLI_TRIS,
86
87 OLI,
89
90 IRS,
92
93 ETM_PLUS,
95
96 TM,
98 MSS,
100}
101
102impl Name for Sensor {
103 fn name(&self) -> &str {
104 match self {
106 Sensor::OLI_TRIS => "OLI+TRIS",
107 Sensor::OLI => "OLI",
108 Sensor::IRS => "IRS",
109 Sensor::ETM_PLUS => "ETM+",
110 Sensor::TM => "TM",
111 Sensor::MSS => "MSS",
112 }
113 }
114}
115
116impl NameLong for Sensor {
117 fn name_long(&self) -> &str {
118 match self {
120 Sensor::OLI_TRIS => "Operational Land Imager+TRIS",
121 Sensor::OLI => "Operational Land Imager",
122 Sensor::IRS => "InfraRed Sensor",
123 Sensor::ETM_PLUS => "Enhanced Thematic Mapper Plus",
124 Sensor::TM => "Thematic Mapper",
125 Sensor::MSS => "Multi Spectral Scanner",
126 }
127 }
128}
129
130fn parse_julian_date(s: &str) -> IResult<&str, NaiveDate> {
131 let (s, year) = date_year(s)?;
132 let (s_out, day_of_year) = take_n_digits::<i64>(3)(s)?;
133 let date = NaiveDate::from_ymd_opt(year, 1, 1)
134 .ok_or_else(|| nom::Err::Error(nom::error::Error::new(s, ErrorKind::Fail)))?
135 + Duration::days(day_of_year - 1);
136 Ok((s_out, date))
137}
138
139#[derive(PartialOrd, PartialEq, Eq, Debug, Clone, Hash)]
145#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
146pub struct SceneId {
147 pub sensor: Sensor,
149
150 pub mission: MissionId,
152
153 pub wrs_path: u32,
154 pub wrs_row: u32,
155
156 pub acquire_date: NaiveDate,
157
158 pub ground_station_identifier: String,
159 pub archive_version_number: u8,
160}
161
162fn parse_sensor(s: &str, mission: u8) -> IResult<&str, Sensor> {
163 alt((
164 map(tag_no_case("c"), |_| Sensor::OLI_TRIS),
165 map(tag_no_case("o"), |_| Sensor::OLI),
166 map(tag_no_case("t"), |_| {
167 if mission == 4 || mission == 5 {
169 Sensor::TM
170 } else {
171 Sensor::IRS
172 }
173 }),
174 map(tag_no_case("e"), |_| Sensor::ETM_PLUS),
175 map(tag_no_case("m"), |_| Sensor::MSS),
176 ))(s)
177}
178
179pub fn parse_scene_id(s: &str) -> IResult<&str, SceneId> {
181 let (s_sensor, _) = tag_no_case("L")(s)?;
182 let (s, _) = take(1usize)(s_sensor)?;
183 let (s, mission): (&str, u8) = take_n_digits_in_range(1, 1..=9)(s)?;
184 let (_, sensor) = parse_sensor(s_sensor, mission)?;
185 let (s, wrs_path) = take_n_digits(3)(s)?;
186 let (s, wrs_row) = take_n_digits(3)(s)?;
187 let (s, acquire_date) = parse_julian_date(s)?;
188 let (s, ground_station_identifier) = take_alphanumeric_n(3)(s)?;
189 let (s, archive_version_number) = take_n_digits(2)(s)?;
190 Ok((
191 s,
192 SceneId {
193 sensor,
194 mission: mission.into(),
195 wrs_path,
196 wrs_row,
197 acquire_date,
198 ground_station_identifier: ground_station_identifier.to_uppercase(),
199 archive_version_number,
200 },
201 ))
202}
203
204#[derive(PartialOrd, PartialEq, Eq, Debug, Clone, Hash)]
207#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
208pub enum ProcessingLevel {
209 L1TP,
210 L1GT,
211 L1GS,
212 L2SP,
213 L2SR,
214 CU,
216 AK,
218 HI,
220 Other(String),
221}
222
223#[derive(PartialOrd, PartialEq, Eq, Debug, Clone, Hash, Copy)]
224#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
225pub enum CollectionCategory {
226 RealTime,
227 Tier1,
228 Tier2,
229 AlbersTier1,
230 AlbersTier2,
231}
232
233impl Name for CollectionCategory {
234 fn name(&self) -> &str {
235 match self {
236 CollectionCategory::RealTime => "RT",
237 CollectionCategory::Tier1 => "T1",
238 CollectionCategory::Tier2 => "T2",
239 CollectionCategory::AlbersTier1 => "A1",
240 CollectionCategory::AlbersTier2 => "A2",
241 }
242 }
243}
244
245impl NameLong for CollectionCategory {
246 fn name_long(&self) -> &str {
247 match self {
248 CollectionCategory::RealTime => "Real-Time",
249 CollectionCategory::Tier1 => "Tier 1",
250 CollectionCategory::Tier2 => "Tier 2",
251 CollectionCategory::AlbersTier1 => "Albers Tier 1",
252 CollectionCategory::AlbersTier2 => "Albers Tier 2",
253 }
254 }
255}
256
257#[derive(PartialOrd, PartialEq, Eq, Debug, Clone, Hash)]
263#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
264pub struct Product {
265 pub sensor: Sensor,
267
268 pub mission: MissionId,
270
271 pub processing_level: ProcessingLevel,
273
274 pub wrs_path: u32,
275 pub wrs_row: u32,
276 pub acquire_date: NaiveDate,
277 pub processing_date: NaiveDate,
278 pub collection_number: u8,
279 pub collection_category: Option<CollectionCategory>,
280}
281
282fn consume_product_sep(s: &str) -> IResult<&str, &str> {
283 tag("_")(s)
284}
285
286fn parse_processing_level(s: &str) -> IResult<&str, ProcessingLevel> {
287 alt((
288 map(tag_no_case("l1tp"), |_| ProcessingLevel::L1TP),
289 map(tag_no_case("l1gs"), |_| ProcessingLevel::L1GS),
290 map(tag_no_case("l1gt"), |_| ProcessingLevel::L1GT),
291 map(tag_no_case("l2sp"), |_| ProcessingLevel::L2SP),
292 map(tag_no_case("l2sr"), |_| ProcessingLevel::L2SR),
293 map(tag_no_case("cu"), |_| ProcessingLevel::CU),
294 map(tag_no_case("ak"), |_| ProcessingLevel::AK),
295 map(tag_no_case("hi"), |_| ProcessingLevel::HI),
296 map(take_alphanumeric, |pl| {
297 ProcessingLevel::Other(pl.to_uppercase())
298 }),
299 ))(s)
300}
301
302fn parse_collection_category(s: &str) -> IResult<&str, CollectionCategory> {
303 alt((
304 map(tag_no_case("rt"), |_| CollectionCategory::RealTime),
305 map(tag_no_case("t1"), |_| CollectionCategory::Tier1),
306 map(tag_no_case("t2"), |_| CollectionCategory::Tier2),
307 map(tag_no_case("a1"), |_| CollectionCategory::AlbersTier1),
308 map(tag_no_case("a2"), |_| CollectionCategory::AlbersTier2),
309 ))(s)
310}
311
312pub fn parse_product(s: &str) -> IResult<&str, Product> {
314 let (s_sensor, _) = tag_no_case("L")(s)?;
315 let (s, _) = take(1usize)(s_sensor)?;
316 let (s, _) = tag("0")(s)?;
317 let (s, mission): (&str, u8) = take_n_digits_in_range(1, 1..=9)(s)?;
318 let (_, sensor) = parse_sensor(s_sensor, mission)?;
319 let (s, _) = consume_product_sep(s)?;
320 let (s, processing_level) = parse_processing_level(s)?;
321 let (s, _) = consume_product_sep(s)?;
322 let (s, wrs_path) = take_n_digits(3)(s)?;
323 let (s, wrs_row) = take_n_digits(3)(s)?;
324 let (s, _) = consume_product_sep(s)?;
325 let (s, acquire_date) = parse_simple_date(s)?;
326 let (s, _) = consume_product_sep(s)?;
327 let (s, processing_date) = parse_simple_date(s)?;
328 let (s, _) = consume_product_sep(s)?;
329 let (s, collection_number) = take_n_digits(2)(s)?;
330 let (s, collection_category) = map(
331 opt(tuple((consume_product_sep, parse_collection_category))),
332 |cc| cc.map(|cc| cc.1),
333 )(s)?;
334 Ok((
335 s,
336 Product {
337 sensor,
338 mission: mission.into(),
339 processing_level,
340 wrs_path,
341 wrs_row,
342 acquire_date,
343 processing_date,
344 collection_number,
345 collection_category,
346 },
347 ))
348}
349
350impl_from_str!(parse_product, Product);
351impl_from_str!(parse_scene_id, SceneId);
352
353#[cfg(test)]
354mod tests {
355 use crate::identifiers::landsat::{
356 parse_julian_date, parse_product, parse_scene_id, CollectionCategory, MissionId,
357 ProcessingLevel, Sensor,
358 };
359 use crate::identifiers::tests::apply_to_samples_from_txt;
360 use chrono::NaiveDate;
361
362 #[test]
363 fn test_parse_julian_date() {
364 let (_, d) = parse_julian_date("2020046").unwrap();
365 assert_eq!(d, NaiveDate::from_ymd_opt(2020, 2, 15).unwrap());
366 }
367
368 #[test]
369 fn test_parse_scene() {
370 let (_, scene) = parse_scene_id("LC80390222013076EDC00").unwrap();
371 assert_eq!(scene.sensor, Sensor::OLI_TRIS);
372 assert_eq!(scene.mission, MissionId::Landsat8);
373 assert_eq!(scene.wrs_path, 39);
374 assert_eq!(scene.wrs_row, 22);
375 assert_eq!(
376 scene.acquire_date,
377 NaiveDate::from_ymd_opt(2013, 3, 17).unwrap()
378 );
379 assert_eq!(scene.ground_station_identifier.as_str(), "EDC");
380 assert_eq!(scene.archive_version_number, 0);
381 }
382
383 #[test]
384 fn test_parse_product_l1() {
385 let (_, product) = parse_product("LC08_L1GT_029030_20151209_20160131_01_RT").unwrap();
386 assert_eq!(product.sensor, Sensor::OLI_TRIS);
387 assert_eq!(product.mission, MissionId::Landsat8);
388 assert_eq!(product.processing_level, ProcessingLevel::L1GT);
389 assert_eq!(
390 product.collection_category,
391 Some(CollectionCategory::RealTime)
392 );
393 }
394
395 #[test]
396 fn test_parse_product_l2() {
397 let (_, product) = parse_product("LC08_L2SP_140041_20130503_20190828_02_T1").unwrap();
398 assert_eq!(product.sensor, Sensor::OLI_TRIS);
399 assert_eq!(product.mission, MissionId::Landsat8);
400 assert_eq!(product.processing_level, ProcessingLevel::L2SP);
401 assert_eq!(product.collection_category, Some(CollectionCategory::Tier1));
402 }
403
404 #[test]
405 fn apply_to_product_testdata() {
406 apply_to_samples_from_txt("landsat_products.txt", |s| {
407 parse_product(s).unwrap();
408 })
409 }
410}