Skip to content

Commit 0c6ecb1

Browse files
authored
PCESA-3224: Support for subdaily granules (#86)
* Added dataset config keys for subdaily and collection concept id, fixed iso-8601 datetime bug * Added unit test with sample cma message * Updated changelog * Fixed changelog, linted code * Moved changes from new release to unreleased in changelog
1 parent d040f2d commit 0c6ecb1

5 files changed

Lines changed: 510 additions & 15 deletions

File tree

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
88
## [Unreleased]
99
### Added
1010
- [issues/59](https://github.com/podaac/bignbit/issues/59): A new pair of keywords (`dataDayStrategy` and `singleDayNumber`) have been added to the DatasetConfiguration for BIG to enable proper image metadata for annual products. These keywords allow a dataset to override the umm-g date info.
11+
- [issues/84](https://github.com/podaac/bignbit/issues/84): New parameter in dataset config `subdaily` that sends DataDateTime to GIBS instead of DataDay.
12+
- Added optional `concept_id` keyword to dataset config to provide an override for finding the proper CMR collection concept ID when testing.
1113
### Changed
1214
### Deprecated
1315
### Removed
1416
### Fixed
17+
- [issues/82](https://github.com/podaac/bignbit/issues/82): Fixed date parsing bug where ISO-8601 format dates, the default for UMM-G, were not handled properly.
1518
- Update gibs_response_queue visibility timeout to match aws_lambda_function handle_gitc_response timeout
1619
### Security
1720

bignbit/generate_image_metadata.py

Lines changed: 42 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import uuid
88
import xml.etree.ElementTree as ET
99
from datetime import datetime, timedelta
10-
from typing import Dict, List, Optional
10+
from typing import Dict, List
1111

1212
from cumulus_logger import CumulusLogger
1313
from cumulus_process import Process
@@ -53,8 +53,7 @@ def process(self) -> List[Dict]:
5353
cma_file_list = [item for sublist in cma_file_list for item in sublist]
5454
granule_umm_json = self.input['granule_umm_json']
5555

56-
# TO DO: perhaps there is a more organized way of managing these
57-
# dataset-level overrides?
56+
# Parse dataset-level overrides
5857
dataset_config = self.input['datasetConfigurationForBIG']['config']
5958
data_day_strat = dataset_config.get('dataDayStrategy')
6059
if data_day_strat is not None and data_day_strat == 'single_day_of_year':
@@ -64,14 +63,26 @@ def process(self) -> List[Dict]:
6463
else:
6564
static_data_day = None
6665

67-
file_metadata_list = generate_metadata(cma_file_list, granule_umm_json, pathlib.Path(f"{self.path}"), static_data_day)
66+
subdaily = dataset_config.get('subdaily', False)
67+
68+
file_metadata_list = generate_metadata(
69+
cma_file_list, granule_umm_json,
70+
pathlib.Path(f"{self.path}"),
71+
static_data_day,
72+
subdaily
73+
)
6874
del self.input['granule_umm_json']
6975
del self.input['big']
7076
self.input['big'] = file_metadata_list
7177
return self.input
7278

7379

74-
def generate_metadata(cma_file_list: List[Dict], granule_umm_json: Dict, temp_dir: pathlib.Path, static_data_day: Optional[int] = None) -> List[Dict]:
80+
def generate_metadata(
81+
cma_file_list: List[Dict],
82+
granule_umm_json: Dict,
83+
temp_dir: pathlib.Path,
84+
static_data_day: int | None = None,
85+
subdaily: bool = False) -> List[Dict]:
7586
"""
7687
For each file in the list, create an ImageMetadata-v1.2 xml file and upload it to s3 in the same
7788
bucket and path as the image file.
@@ -89,6 +100,8 @@ def generate_metadata(cma_file_list: List[Dict], granule_umm_json: Dict, temp_di
89100
static_data_day
90101
Optionally, the DatasetConfiguration can override the date metadata in the
91102
granule umm-json
103+
subdaily
104+
boolean flag if product is subdaily (if True, add DataDateTime to metadata)
92105
93106
Returns
94107
-------
@@ -133,7 +146,7 @@ def generate_metadata(cma_file_list: List[Dict], granule_umm_json: Dict, temp_di
133146

134147
# If this is a browse image, generate image metadata for it and upload it to s3
135148
if granule_type == 'browse':
136-
image_metadata_xml = create_metadata_xml(begin, mid, end, dataday, partial_id)
149+
image_metadata_xml = create_metadata_xml(begin, mid, end, dataday, subdaily, partial_id)
137150
temp_xml_path = write_image_metadata_xml(image_metadata_xml, temp_dir)
138151
image_metadata_xml_metadata = get_file_metadata_for_image_metadta_xml(temp_xml_path, cnm_file_meta)
139152
s3_uri = upload_image_metadata_xml(image_metadata_xml_metadata, temp_xml_path)
@@ -267,7 +280,7 @@ def transform_files_to_cnm_product_files(cma_file_meta: Dict, file_type: str, su
267280
return cnm_file_meta
268281

269282

270-
def extract_granule_dates(granule_umm_json: dict, static_data_day: Optional[int] = None) -> tuple[str, str, str, str]:
283+
def extract_granule_dates(granule_umm_json: dict, static_data_day: int | None = None) -> tuple[str, str, str, str]:
271284
"""
272285
Parse the begin, midpoint, end, and dataday for this granule
273286
@@ -326,10 +339,21 @@ def parse_datetime(datetime_str: str) -> datetime:
326339
datetime
327340
a datetime object
328341
"""
329-
try:
330-
return datetime.strptime(datetime_str, "%Y-%m-%dT%H:%M:%S.%fZ")
331-
except ValueError:
332-
return datetime.strptime(datetime_str, "%Y-%m-%dT%H:%M:%SZ")
342+
formats = [
343+
"%Y-%m-%dT%H:%M:%S.%fZ", # 2023-01-01T12:30:45.123456Z
344+
"%Y-%m-%dT%H:%M:%SZ", # 2023-01-01T12:30:45Z
345+
"%Y-%m-%dT%H:%M:%S.%f%z", # 2023-01-01T12:30:45.123456+00:00
346+
"%Y-%m-%dT%H:%M:%S%z", # 2023-01-01T12:30:45+00:00
347+
]
348+
349+
for fmt in formats:
350+
try:
351+
return datetime.strptime(datetime_str, fmt)
352+
except ValueError:
353+
continue
354+
355+
# If none of the formats worked
356+
raise ValueError(f"Unable to parse datetime string: {datetime_str}")
333357

334358

335359
def parse_doy(year: int, doy: int) -> str:
@@ -354,7 +378,7 @@ def parse_doy(year: int, doy: int) -> str:
354378

355379

356380
def create_metadata_xml(beginning_time: str, middle_time: str, ending_time: str, dataday: str,
357-
partial_id: str = None) -> ET.ElementTree:
381+
subdaily: bool = False, partial_id: str | None = None) -> ET.ElementTree:
358382
"""
359383
Create an ImageMetadata-v1.2 XML Element tree
360384
@@ -368,6 +392,8 @@ def create_metadata_xml(beginning_time: str, middle_time: str, ending_time: str,
368392
formatted datetime string for data end date time
369393
dataday
370394
string if format %Y%j for day of year the data represents
395+
subdaily
396+
boolean flag if product is subdaily (if True, add DataDateTime to metadata)
371397
partial_id
372398
partial id associated with data
373399
@@ -384,7 +410,10 @@ def create_metadata_xml(beginning_time: str, middle_time: str, ending_time: str,
384410
ET.SubElement(imagery_metadata, "DataStartDateTime").text = beginning_time
385411
ET.SubElement(imagery_metadata, "DataMidDateTime").text = middle_time
386412
ET.SubElement(imagery_metadata, "DataEndDateTime").text = ending_time
387-
ET.SubElement(imagery_metadata, "DataDay").text = dataday
413+
if subdaily:
414+
ET.SubElement(imagery_metadata, "DataDateTime").text = beginning_time
415+
else:
416+
ET.SubElement(imagery_metadata, "DataDay").text = dataday
388417
if partial_id:
389418
ET.SubElement(imagery_metadata, "PartialId").text = partial_id
390419

bignbit/get_collection_concept_id.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,11 @@ def process(self) -> dict:
3333
collection_shortname = self.config['collection_shortname']
3434
cmr_provider = self.config['cmr_provider']
3535
cmr_environment = self.config['cmr_environment']
36-
collection_id = get_collection_concept_id(collection_shortname, cmr_provider, cmr_environment)
36+
dataset_config = self.input['datasetConfigurationForBIG']['config']
37+
# Use override for collection concept id from dataset config if provided
38+
collection_id = dataset_config.get('concept_id')
39+
if collection_id is None:
40+
collection_id = get_collection_concept_id(collection_shortname, cmr_provider, cmr_environment)
3741
self.input['collection_concept_id'] = collection_id
3842
return self.input
3943

0 commit comments

Comments
 (0)