diff --git a/python/packages/nisar/static/granule_id.py b/python/packages/nisar/static/granule_id.py index 0f7198347..e13e7277e 100644 --- a/python/packages/nisar/static/granule_id.py +++ b/python/packages/nisar/static/granule_id.py @@ -1,27 +1,33 @@ import string from datetime import datetime +from osgeo import osr import isce3 -def int_to_3_digit_string(i: int) -> str: +def int_to_n_digit_string(i: int, n: int = 3) -> str: """ - Format an integer as a 3-digit zero-padded string. + Format an integer as an n-digit zero-padded string. Parameters ---------- i : int - The input integer. Must be >= 0 and <= 999. + The input integer. Must be >= 0 and <= 10^n - 1. + + n : int + The number of digits in the output string. Returns ------- str - A 3-character string representation of the input integer, left-padded with - zeros. + An n-character string representation of the input integer, left-padded + with zeros. """ - if (i < 0) or (i > 999): - raise ValueError(f"argument must be in the range [0, 999], got {i}") - return f"{i:03d}" + max_value = 10 ** n - 1 + if (i < 0) or (i > max_value): + raise ValueError(f"argument must be in the range [0, {max_value}]," + f" got {i}") + return f"{i:0{n}d}" def orbit_direction_to_char_code(direction: isce3.core.OrbitPassDirection) -> str: @@ -99,6 +105,7 @@ def form_granule_id( frame_number: int, x_posting: float, y_posting: float, + epsg_code: int, validity_start_datetime: datetime, composite_release_id: str, processing_center: str, @@ -129,6 +136,9 @@ def form_granule_id( x_posting, y_posting : float X and Y spacing of the raster coordinate grid, in the units of the grid's native coordinate system. Must be > 0 and <= 999. + epsg_code : int + EPSG code of the spatial reference system in which the raster coordinate grid is + defined. validity_start_datetime : datetime.datetime UTC date and time of the start of the granule's validity date range. Must not contain a fractional seconds component. @@ -144,29 +154,56 @@ def form_granule_id( Returns ------- str - The granule ID. + The granule ID. If the spatial reference system specified by + epsg_code is projected, the X and Y postings are multiplied + by 10 and formatted as four-digit, zero-padded integers. + If the spatial reference system is geographic, the X and Y + postings are formatted directly (without multiplication) as + four-digit, zero-padded integers, with the decimal point + removed. References ---------- .. [1] S. Niemoeller, "NASA SDS Product Specification Level-2 Static Layers", JPL D-107727, 2025. """ - template = string.Template( - "${MISSION}_${I}${L}_${PROD}_${REL}_${P}_${FRM}_${Xposting}_${Yposting}" - "_${ValidityStartDateTime}_${CRID}_${LOC}_${CTR}" - ) + srs = osr.SpatialReference() + srs.ImportFromEPSG(epsg_code) + + # The granule ID for projected coordinate systems encodes the X and Y + # postings in decimeters as 4-digit zero-padded integers, + # while the granule ID for geographic coordinate systems encodes the + # X and Y postings in degrees as 4-digit zero-padded integers with the + # decimal point removed. + if not srs.IsGeographic(): + template = string.Template( + "${MISSION}_${I}${L}_${PROD}_${REL}_${P}_${FRM}" + "_${XpostingDecimeters}_${YpostingDecimeters}" + "_${ValidityStartDateTime}_${CRID}_${LOC}_${CTR}" + ) + else: + template = string.Template( + "${MISSION}_${I}${L}_${PROD}_${REL}_${P}_${FRM}" + "_${Xposting}_${Yposting}" + "_${ValidityStartDateTime}_${CRID}_${LOC}_${CTR}" + ) return template.substitute( MISSION=mission_id, I=radar_band, L=product_level, PROD=product_type, - REL=int_to_3_digit_string(relative_orbit_number), + REL=int_to_n_digit_string(relative_orbit_number, n=3), P=orbit_direction_to_char_code(orbit_pass_direction), - FRM=int_to_3_digit_string(frame_number), - Xposting=int_to_3_digit_string(int(round(x_posting))), - Yposting=int_to_3_digit_string(int(round(y_posting))), - ValidityStartDateTime=datetime_to_yyyymmddthhmmss(validity_start_datetime), + FRM=int_to_n_digit_string(frame_number, 3), + XpostingDecimeters=int_to_n_digit_string(int(10*round(x_posting)), + n=4), + YpostingDecimeters=int_to_n_digit_string(int(10*round(y_posting)), + n=4), + Xposting=int_to_n_digit_string(int(round(x_posting)), n=4), + Yposting=int_to_n_digit_string(int(round(y_posting)), n=4), + ValidityStartDateTime=datetime_to_yyyymmddthhmmss( + validity_start_datetime), CRID=composite_release_id, LOC=processing_center_to_char_code(processing_center), - CTR=int_to_3_digit_string(product_counter), + CTR=int_to_n_digit_string(product_counter, n=3), ) diff --git a/python/packages/nisar/workflows/static.py b/python/packages/nisar/workflows/static.py index e7165519b..84afaf022 100644 --- a/python/packages/nisar/workflows/static.py +++ b/python/packages/nisar/workflows/static.py @@ -236,6 +236,7 @@ def run_static_layers_workflow(config_file: os.PathLike | str) -> None: orbit_pass_direction=orbit_pass_direction, x_posting=abs(geo_grid.spacing_x), y_posting=abs(geo_grid.spacing_y), + epsg_code=geo_grid_params["epsg"], validity_start_datetime=validity_start_datetime, composite_release_id=primary_executable_params["composite_release_id"], processing_center=primary_executable_params["processing_center"], diff --git a/tests/python/packages/nisar/static/granule_id.py b/tests/python/packages/nisar/static/granule_id.py index f0455a372..1397923f8 100644 --- a/tests/python/packages/nisar/static/granule_id.py +++ b/tests/python/packages/nisar/static/granule_id.py @@ -14,13 +14,14 @@ def test_form_granule_id(): frame_number=2, x_posting=10.0, y_posting=5.0, + epsg_code=32611, validity_start_datetime=datetime.fromisoformat("1999-12-31T23:59:59"), composite_release_id="T01023", processing_center="JPL", product_counter=1, ) assert ( - granule_id == "NISAR_L2_STATIC_001_A_002_010_005_19991231T235959_T01023_J_001" + granule_id == "NISAR_L2_STATIC_001_A_002_0100_0050_19991231T235959_T01023_J_001" ) granule_id = form_granule_id( @@ -33,11 +34,12 @@ def test_form_granule_id(): frame_number=124, x_posting=80.0, y_posting=80.0, + epsg_code=32611, validity_start_datetime=datetime.fromisoformat("2000-01-01T00:00:00"), composite_release_id="A11111", processing_center="somewhere else", product_counter=999, ) assert ( - granule_id == "NISAR_S2_STATIC_123_D_124_080_080_20000101T000000_A11111_X_999" + granule_id == "NISAR_S2_STATIC_123_D_124_0800_0800_20000101T000000_A11111_X_999" )