Skip to content

Commit 3603cbd

Browse files
committed
issues/150: Added support for NRT filtering by granule id, minor fixes
1 parent d7da221 commit 3603cbd

7 files changed

Lines changed: 267 additions & 58 deletions

File tree

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99
### Added
10+
- [issues/150](https://github.com/podaac/bignbit/issues/150): Added support for mixed NRT & Standard collections with configurable regex.
1011
### Changed
12+
- Removed MD5 checksum hash computation to optimize performance of `handle_big_result` lambda.
1113
### Deprecated
1214
### Removed
1315
### Fixed
16+
- [issues/83](https://github.com/podaac/bignbit/issues/83): Use "_" instead of ":" in CNM filenames for Mac and Linux compatibility
1417
### Security
1518
- [issues/151](https://github.com/podaac/bignbit/issues/151): Added ExpectedBucketOwner parameter when making S3 requests.
1619

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,7 @@ The contents of the configuration file should be a valid json object with the fo
167167
| sendToHarmony | boolean | true/false if this collection should be processed using Harmony to generate browse images |
168168
| operaHLSTreatment | boolean | true/false if this collection should have special OPERA_L3_DSWX-HLS processing applied to it (see [apply_opera_hls_treatment](bignbit/apply_opera_hls_treatment.py)) |
169169
| imageFilenameRegex | string | Regular expression used to identify which file in a granule should be used as the image file. Uses first if multiple files match |
170+
| nrtFilenameRegex | string | Regular expression used to identify whether a granule should be treated as NRT or standard. All files are assumed standard if not specified. |
170171
| imgVariables | list(object) | List of JSON objects with at least one attribute called `id` whose value is the name of a variable to generate an image for. `all` can be used in cases where the collection does not have variables or all variables in the collection should have images generated |
171172
| height | int | [OPTIONAL] Controls the height of the output image from Harmony (see https://github.com/nasa/harmony-browse-image-generator?tab=readme-ov-file#dimensions--scale-sizes) |
172173
| width | int | [OPTIONAL] Controls the width of the output image from Harmony (see https://github.com/nasa/harmony-browse-image-generator?tab=readme-ov-file#dimensions--scale-sizes) |

bignbit/handle_big_result.py

Lines changed: 56 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import xml.etree.ElementTree as ET
1111
from datetime import datetime, timezone
1212
from pathlib import Path
13+
import re
1314
from typing import Any
1415
from urllib.parse import urlparse
1516

@@ -73,20 +74,7 @@ def process(self) -> dict[str, Any]:
7374
# Throw KeyError if the state input doesn't contain the dataset config
7475
dataset_config = self.input['datasetConfigurationForBIG']['config']
7576

76-
data_day_strat = dataset_config.get('dataDayStrategy')
77-
if data_day_strat is not None and data_day_strat == 'single_day_of_year':
78-
static_data_day = dataset_config.get('singleDayNumber', 1)
79-
# Throw TypeError on bad configuration
80-
static_data_day = int(static_data_day)
81-
else:
82-
static_data_day = None
83-
84-
if static_data_day is not None and (static_data_day < 1 or static_data_day > 366):
85-
CUMULUS_LOGGER.warning(
86-
f'Specified data day override {static_data_day} is not logical '
87-
'as a day of year. Defaulting to doy 001.'
88-
)
89-
static_data_day = 1
77+
static_data_day = _resolve_static_data_day(dataset_config)
9078

9179
subdaily = dataset_config.get('subdaily', False)
9280
try:
@@ -102,6 +90,13 @@ def process(self) -> dict[str, Any]:
10290
bignbit_audit_path = self.config.get('bignbit_audit_path')
10391
granule_id = self.input['granules'][0]['granuleId']
10492

93+
nrt_filename_regex = dataset_config.get('nrtFilenameRegex')
94+
is_nrt = False
95+
if nrt_filename_regex:
96+
nrt_match = re.match(nrt_filename_regex, granule_id)
97+
if nrt_match:
98+
is_nrt = True
99+
105100
try:
106101
partial_id = utils.extract_mgrs_grid_code(granule_umm_json)
107102
except KeyError:
@@ -160,6 +155,7 @@ def process(self) -> dict[str, Any]:
160155
cmr_provider,
161156
collection_name,
162157
granule_id,
158+
is_nrt,
163159
bignbit_audit_bucket,
164160
bignbit_audit_path
165161
)
@@ -179,6 +175,34 @@ def process(self) -> dict[str, Any]:
179175
return response_payload
180176

181177

178+
def _resolve_static_data_day(dataset_config: dict) -> int | None:
179+
"""
180+
Determine the static data day override from dataset config, if configured.
181+
182+
Parameters
183+
----------
184+
dataset_config : dict
185+
Dataset configuration dictionary
186+
187+
Returns
188+
-------
189+
int | None
190+
The static day-of-year override (1–366), or None if not configured.
191+
Falls back to 1 if the configured value is outside the valid range.
192+
"""
193+
if dataset_config.get('dataDayStrategy') != 'single_day_of_year':
194+
return None
195+
# Throw TypeError on bad configuration
196+
static_data_day = int(dataset_config.get('singleDayNumber', 1))
197+
if static_data_day < 1 or static_data_day > 366:
198+
CUMULUS_LOGGER.warning(
199+
f'Specified data day override {static_data_day} is not logical '
200+
'as a day of year. Defaulting to doy 001.'
201+
)
202+
return 1
203+
return static_data_day
204+
205+
182206
def process_harmony_results(harmony_job: dict[str, str], cmr_env: str) -> list[dict[str, Any]]:
183207
"""
184208
Process the results of a Harmony job
@@ -216,18 +240,17 @@ def process_harmony_results(harmony_job: dict[str, str], cmr_env: str) -> list[d
216240
for url in result_urls:
217241
bucket, key = urlparse(url).netloc, urlparse(url).path.lstrip('/')
218242

219-
response = s3_client.get_object(Bucket=bucket, Key=key,
220-
ExpectedBucketOwner=utils.get_aws_account_id())
221-
md5_hash = hashlib.new('md5')
222-
for chunk in response['Body'].iter_chunks(chunk_size=100 * 1024 * 1024): # 100 MB chunk size
223-
md5_hash.update(chunk)
243+
response = s3_client.head_object(Bucket=bucket, Key=key,
244+
ExpectedBucketOwner=utils.get_aws_account_id())
245+
# For single-part uploads, the S3 ETag is the MD5 hex digest of the object
246+
etag = response['ETag'].strip('"')
224247

225248
filename = key.split('/')[-1]
226249
file_dict = {
227250
'fileName': filename,
228251
'bucket': bucket,
229252
'key': key,
230-
'checksum': md5_hash.hexdigest(),
253+
'checksum': etag,
231254
'checksumType': 'md5'
232255
}
233256
# Weird quirk where if we are working with a collection that doesn't define variables, the Harmony request
@@ -392,6 +415,7 @@ def write_cnm_message(
392415
cmr_provider: str,
393416
collection_name: str,
394417
granule_id: str,
418+
is_nrt: bool,
395419
bignbit_audit_bucket: str,
396420
bignbit_audit_path: str,
397421
) -> str:
@@ -408,6 +432,8 @@ def write_cnm_message(
408432
Collection that this image set belongs to
409433
granule_id: str
410434
Granule id (used to determine CNM filename)
435+
is_nrt: bool
436+
True if the filename of the image_set is considered NRT
411437
bignbit_audit_bucket: str
412438
staging bucket where CNM is uploaded (default is *-internal)
413439
bignbit_audit_path: str
@@ -418,11 +444,11 @@ def write_cnm_message(
418444
s3_key: str
419445
s3 key pointing to the uploaded CNM message
420446
"""
421-
cnm_message = construct_cnm(image_set, cmr_provider, collection_name)
447+
cnm_message = construct_cnm(image_set, cmr_provider, collection_name, is_nrt)
422448
cnm_bytes = json.dumps(cnm_message).encode()
423449
submission_time = cnm_message.get('submissionTime')
424450
collection_fullname = cnm_message.get('collection')
425-
cnm_key = f'{bignbit_audit_path}/{collection_fullname}/{granule_id}.{submission_time}.cnm.json'
451+
cnm_key = f'{bignbit_audit_path}/{collection_fullname}/{granule_id}.{submission_time}.cnm.json'.replace(':', '_')
426452
utils.upload_object(
427453
cnm_bytes,
428454
bignbit_audit_bucket,
@@ -435,19 +461,22 @@ def write_cnm_message(
435461
def construct_cnm(
436462
image_set: ImageSet,
437463
cmr_provider: str,
438-
collection_name: str
464+
collection_name: str,
465+
is_nrt: bool,
439466
) -> dict[str, Any]:
440467
"""
441468
Construct the CNM message for GITC
442469
443470
Parameters
444471
----------
445472
image_set: ImageSet
446-
ImageSet for one image to be sent to gibs
473+
ImageSet for one image to be sent to gibs
447474
cmr_provider: str
448475
The provider sent in the CNM message
449476
collection_name: str
450477
Collection that this image set belongs to
478+
is_nrt: bool
479+
True if the filename of the image_set is considered NRT
451480
452481
Returns
453482
----------
@@ -457,11 +486,12 @@ def construct_cnm(
457486
product = to_cnm_product_dict(image_set)
458487
submission_time = datetime.now(timezone.utc).isoformat()[:-9] + 'Z'
459488
CUMULUS_LOGGER.debug(image_set.image['variable'])
489+
nrt_string = '_NRT' if is_nrt else ''
460490
if 'output_crs' in image_set.image:
461491
crs_suffix = GIBS_CRS_NAME_TO_SUFFIX.get(image_set.image.get('output_crs', 'EPSG:4326'), 'LL')
462-
new_collection = f"{collection_name}_{image_set.image['variable']}_{crs_suffix}".replace('/', '_')
492+
new_collection = f"{collection_name}_{image_set.image['variable']}{nrt_string}_{crs_suffix}".replace('/', '_')
463493
else:
464-
new_collection = f"{collection_name}_{image_set.image['variable']}".replace('/', '_')
494+
new_collection = f"{collection_name}_{image_set.image['variable']}{nrt_string}".replace('/', '_')
465495

466496
return {
467497
'version': '1.5.1',

scripts/CONFIGURATION.md

Lines changed: 33 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -21,39 +21,51 @@ To configure a collection for bignbit, you need to have the following informatio
2121
shortName Short name of the collection
2222
2323
--collectionId COLLECTIONID
24-
CMR collection id, use if the collection name is ambiguous.
25-
WARNING: since collection ids are venue-specific, the output config will need to be re-generated when deploying to a separate venue.
26-
24+
CMR collection id, use if the collection name is ambiguous. WARNING: since
25+
collection ids are venue-specific, the output config will need to be re-
26+
generated when deploying to a separate venue.
27+
--no-sendToHarmony Specifies that the collection should not be processed by Harmony API.
28+
Default is `sendToHarmony = True`.
29+
--operaHLSTreatment Specifies whether the collection should receive the "OPERA HLS Treatment".
30+
Default is `operaHLSTreatment = False`
2731
--imageFilenameRegex IMAGEFILENAMEREGEX
28-
Regular expression used to identify which file in a granule should be used as the image file.
29-
Uses first if multiple files match
30-
32+
Regular expression used to identify which file in a granule should be used
33+
as the image file. Uses first if multiple files match
34+
--nrtFilenameRegex NRTFILENAMEREGEX
35+
Regular expression used to identify whether a granule should be treated as
36+
NRT or standard. All files are assumed standard if not specified.
3137
--imgVariables VAR_NAME [VAR_NAME ...]
32-
List of variable names (not UMM-Var concept ids) to use for generating browse image(s).
33-
If none are provided, the default of "all" is used
34-
38+
List of variable names (not UMM-Var concept ids) to use for generating
39+
browse image(s). If none are provided, the default of "all" is used
3540
--dimensions HEIGHT WIDTH
36-
Optional override to specify the height and width that each browse image should have when processed by Harmony.
37-
41+
Optional override to specify the height and width that each browse image
42+
should have when processed by Harmony.
3843
--scaleExtentPolar MINX MINY MAXX MAXY
3944
Controls the geographic extent of polar-projected browse image outputs.
40-
This keyword is ignored if `outputCrs` does not contain EPSG:3413 or EPSG:3031
41-
(polar stereographic projections used by GIBS)
42-
45+
This keyword is ignored if `outputCrs` does not contain EPSG:3413 or
46+
EPSG:3031 (polar stereographic projections used by GIBS)
4347
--singleDayNumber JJJ
44-
All granules in this dataset will use the day of year specified in this keyword. ex: "001" for January 1st
45-
46-
--subdaily Set to true if granules contain subdaily data. This will send `DataDateTime` metadata to GIBS as described in the GIBS ICD
47-
48-
--outputCrs list of CRS strings from these options: EPSG:4326,EPSG:3413,EPSG:3031,EPSG:3857
49-
List of output projections or coordinate reference systems for which to produce browse images. Applies to all variables in granule. GIBS-compatible values are EPSG:4326, EPSG:3413, or EPSG:3031
48+
All granules in this dataset will use the day of year specified in this
49+
keyword. ex: "001" for January 1st
50+
--subdaily Set to true if granules contain subdaily data. This will send
51+
`DataDateTime` metadata to GIBS as described in the GIBS ICD
52+
--outputCrs {EPSG:4326,EPSG:3413,EPSG:3031,EPSG:3857} [{EPSG:4326,EPSG:3413,EPSG:3031,EPSG:3857} ...]
53+
List of output projections or coordinate reference systems for which to
54+
produce browse images. Applies to all variables in granule. GIBS-
55+
compatible values are EPSG:4326, EPSG:3413, or EPSG:3031
56+
--reprojection Set to true to force reprojection for all granules. This will send the
57+
`outputCrs` parameter in Harmony API calls unconditionally. Warning: if
58+
the Harmony service used to generate browse images does not support
59+
reprojection, jobs will fail.
5060
```
5161

5262
Additionally, you can provide the `--s3Destination` keyword if you wish to upload the dataset config directly to s3. You must have valid access credentials to your bucket in the environment you run the script if you wish to use this option.
5363

5464
```
5565
--s3Destination BUCKET KEY
56-
Specify an S3 URI (bucket key, ex: --s3Destination podaac-bignbit-sit-svc-internal big-config) to upload the config. Do not specify the filename, it is auto-generated.
66+
Specify an S3 URI (bucket key, ex: --s3Destination podaac-bignbit-sit-svc-
67+
internal big-config) to upload the config. Do not specify the filename, it
68+
is auto-generated.
5769
```
5870

5971
## Setup

scripts/create_dataset_config.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -303,7 +303,7 @@ def cli() -> argparse.Namespace:
303303
parser.add_argument(
304304
'shortName',
305305
type=str,
306-
help='Short name of the collection, '
306+
help='Short name of the collection'
307307
)
308308
parser.add_argument(
309309
'--collectionId',
@@ -338,6 +338,14 @@ def cli() -> argparse.Namespace:
338338
'the image file. Uses first if multiple files match'
339339
)
340340
)
341+
parser.add_argument(
342+
'--nrtFilenameRegex',
343+
type=str,
344+
help=(
345+
'Regular expression used to identify whether a granule should be treated as NRT or '
346+
'standard. All files are assumed standard if not specified.'
347+
)
348+
)
341349
parser.add_argument(
342350
'--imgVariables',
343351
type=str,

0 commit comments

Comments
 (0)