Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Eccodes 2v36 #504

Merged
merged 4 commits into from
Jul 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions docs/ref/release_notes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ Features
grid definition template 20 - polar stereographic.
`(PR#405) <https://github.com/SciTools/iris-grib/pull/405>`_


Documentation
^^^^^^^^^^^^^
* `@pp-mo <https://github.com/pp-mo>`_ reworked the main docs page to :
Expand All @@ -43,6 +44,15 @@ Dependencies
* `@bjlittle <https://github.com/bjlittle>`_ migrated to ``pytest``.
`(PR#420) <https://github.com/SciTools/iris-grib/pull/420>`_

* `@pp-mo <https://github.com/pp-mo>`_ enabled support for
`eccodes v2.36 <https://confluence.ecmwf.int/display/ECC/ecCodes+version+2.36.0+released>`_.
Eccodes v2.36 has implemented some backwards incompatible changes :
The ``indicatorOfUnitOfTimeRange`` key was removed, to be replaced with
``indicatorOfUnitForForecastTime`` (but only in GRIB v2 messages only, not GRIB 1);
and the ``iScansPositively`` and ``jScansPositively`` keys became read-only.
The resulting changes mean **we now only support eccodes >=2.33**.
`(PR#504) <https://github.com/SciTools/iris-grib/issues/504>`_


What's new in iris-grib v0.19.1
-------------------------------
Expand Down
2 changes: 1 addition & 1 deletion requirements/py310.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ dependencies:
# Core dependencies.
- iris>=3.0.2
- python-eccodes>=1.6.1
- eccodes>=2.32.1
- eccodes>=2.33

# Optional dependencies.
- mo_pack
Expand Down
2 changes: 1 addition & 1 deletion requirements/py311.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ dependencies:
# Core dependencies.
- iris>=3.0.2
- python-eccodes>=1.6.1
- eccodes>=2.32.1
- eccodes>=2.33

# Optional dependencies.
- mo_pack
Expand Down
2 changes: 1 addition & 1 deletion requirements/py312.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ dependencies:
# Core dependencies.
- iris>=3.0.2
- python-eccodes>=1.6.1
- eccodes>=2.32.1
- eccodes>=2.33

# Optional dependencies.
- mo_pack
Expand Down
18 changes: 9 additions & 9 deletions src/iris_grib/_load_convert.py
Original file line number Diff line number Diff line change
Expand Up @@ -1530,26 +1530,26 @@ def ensure_surface_air_pressure_name(cube):
return {"standard_name": "surface_air_pressure"}


def time_range_unit(indicatorOfUnitOfTimeRange):
def time_range_unit(indicatorOfUnitForForecastTime):
"""
Translate the time range indicator to an equivalent
:class:`cf_units.Unit`.

Args:

* indicatorOfUnitOfTimeRange:
* indicatorOfUnitForForecastTime:
Message section 4, octet 18.

Returns:
:class:`cf_units.Unit`.

"""
try:
unit = Unit(_TIME_RANGE_UNITS[indicatorOfUnitOfTimeRange])
unit = Unit(_TIME_RANGE_UNITS[indicatorOfUnitForForecastTime])
except (KeyError, ValueError):
msg = (
"Product definition section 4 contains unsupported "
"time range unit [{}]".format(indicatorOfUnitOfTimeRange)
"time range unit [{}]".format(indicatorOfUnitForForecastTime)
)
raise TranslationError(msg)
return unit
Expand Down Expand Up @@ -1755,13 +1755,13 @@ def vertical_coords(section, metadata):
metadata["aux_coords_and_dims"].append((coord, None))


def forecast_period_coord(indicatorOfUnitOfTimeRange, forecastTime):
def forecast_period_coord(indicatorOfUnitForForecastTime, forecastTime):
"""
Create the forecast period coordinate.

Args:

* indicatorOfUnitOfTimeRange:
* indicatorOfUnitForForecastTime:
Message section 4, octets 18.

* forecastTime:
Expand All @@ -1772,7 +1772,7 @@ def forecast_period_coord(indicatorOfUnitOfTimeRange, forecastTime):

"""
# Determine the forecast period and associated units.
unit = time_range_unit(indicatorOfUnitOfTimeRange)
unit = time_range_unit(indicatorOfUnitForForecastTime)
point = unit.convert(forecastTime, "hours")
# Create the forecast period scalar coordinate.
coord = DimCoord(point, standard_name="forecast_period", units="hours")
Expand Down Expand Up @@ -1816,7 +1816,7 @@ def statistical_forecast_period_coord(section, frt_coord):
if options.support_hindcast_values:
# Apply the hindcast fix.
forecast_time = _hindcast_fix(forecast_time)
forecast_units = time_range_unit(section["indicatorOfUnitOfTimeRange"])
forecast_units = time_range_unit(section["indicatorOfUnitForForecastTime"])
forecast_seconds = forecast_units.convert(forecast_time, "seconds")
start_time_delta = timedelta(seconds=forecast_seconds)

Expand Down Expand Up @@ -1958,7 +1958,7 @@ def time_coords(section, metadata, rt_coord):

# Calculate the forecast period coordinate.
fp_coord = forecast_period_coord(
section["indicatorOfUnitOfTimeRange"], forecast_time
section["indicatorOfUnitForForecastTime"], forecast_time
)
# Add the forecast period coordinate to the metadata aux coords.
metadata["aux_coords_and_dims"].append((fp_coord, None))
Expand Down
16 changes: 9 additions & 7 deletions src/iris_grib/_save_rules.py
Original file line number Diff line number Diff line change
Expand Up @@ -230,12 +230,14 @@ def dx_dy(x_coord, y_coord, grib):


def scanning_mode_flags(x_coord, y_coord, grib):
eccodes.codes_set_long(
grib, "iScansPositively", int(x_coord.points[1] - x_coord.points[0] > 0)
)
eccodes.codes_set_long(
grib, "jScansPositively", int(y_coord.points[1] - y_coord.points[0] > 0)
)
x_positive = x_coord.points[1] - x_coord.points[0] > 0
y_positive = y_coord.points[1] - y_coord.points[0] > 0
scanningMode = 0
if not x_positive:
scanningMode |= 0x80 # "bit 1" has negative sense : set=decreasing
if y_positive:
scanningMode |= 0x40 # "bit2" has positive sense : set=increasing
eccodes.codes_set_long(grib, "scanningMode", scanningMode)
trexfeathers marked this conversation as resolved.
Show resolved Hide resolved
trexfeathers marked this conversation as resolved.
Show resolved Hide resolved


def horizontal_grid_common(cube, grib, xy=False):
Expand Down Expand Up @@ -977,7 +979,7 @@ def set_forecast_time(cube, grib):
else:
_, _, fp, grib_time_code = _missing_forecast_period(cube)

eccodes.codes_set(grib, "indicatorOfUnitOfTimeRange", grib_time_code)
eccodes.codes_set(grib, "indicatorOfUnitForForecastTime", grib_time_code)
eccodes.codes_set(grib, "forecastTime", fp)


Expand Down
2 changes: 2 additions & 0 deletions src/iris_grib/message.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
"longitude": "longitudes",
"latitudes": "latitude",
"longitudes": "longitude",
# Support older form of key which used to exist before eccodes 2v36
"indicatorOfUnitForForecastTime": "indicatorOfUnitOfTimeRange",
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@

class Test(tests.IrisGribTest):
def test(self):
# (indicatorOfUnitOfTimeRange, forecastTime, expected-hours)
# (indicatorOfUnitForForecastTime, forecastTime, expected-hours)
times = [
(0, 60, 1), # minutes
(1, 2, 2), # hours
Expand All @@ -29,8 +29,8 @@ def test(self):
(13, 3600, 1),
] # seconds

for indicatorOfUnitOfTimeRange, forecastTime, hours in times:
coord = forecast_period_coord(indicatorOfUnitOfTimeRange, forecastTime)
for indicatorOfUnitForForecastTime, forecastTime, hours in times:
coord = forecast_period_coord(indicatorOfUnitForForecastTime, forecastTime)
self.assertIsInstance(coord, DimCoord)
self.assertEqual(coord.standard_name, "forecast_period")
self.assertEqual(coord.units, "hours")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ def section_4():
return {
"hoursAfterDataCutoff": MDI,
"minutesAfterDataCutoff": MDI,
"indicatorOfUnitOfTimeRange": 0, # minutes
"indicatorOfUnitForForecastTime": 0, # minutes
"forecastTime": 360,
"NV": 0,
"typeOfFirstFixedSurface": 103,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ def section_4_sample():
"productDefinitionTemplateNumber": 15,
"hoursAfterDataCutoff": MDI,
"minutesAfterDataCutoff": MDI,
"indicatorOfUnitOfTimeRange": 0, # minutes
"indicatorOfUnitForForecastTime": 0, # minutes
"forecastTime": 360,
"NV": 0,
"typeOfFirstFixedSurface": 103,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ def setUp(self):
"hoursAfterDataCutoff": _MDI,
"minutesAfterDataCutoff": _MDI,
"constituentType": 1,
"indicatorOfUnitOfTimeRange": 0, # minutes
"indicatorOfUnitForForecastTime": 0, # minutes
"startStep": 360,
"NV": 0,
"typeOfFirstFixedSurface": 103,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ def setUp(self):
self.section["minuteOfEndOfOverallTimeInterval"] = 0
self.section["secondOfEndOfOverallTimeInterval"] = 0
self.section["forecastTime"] = mock.Mock()
self.section["indicatorOfUnitOfTimeRange"] = mock.Mock()
self.section["indicatorOfUnitForForecastTime"] = mock.Mock()

def test_basic(self):
coord = statistical_forecast_period_coord(self.section, self.frt_coord)
Expand Down
10 changes: 10 additions & 0 deletions src/iris_grib/tests/unit/save_rules/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,3 +84,13 @@ def _check_key(self, name, value):
self.assertEqual(0, 1, msg_fmt.format(name, value, "((UNSET))"))
else:
self.assertArrayEqual(found, value, msg_fmt.format(name, value, found))

def _check_scanmode(self, x_direction, y_direction):
expected = 0
if x_direction < 0:
# "bit 1" set if x scans negatively
expected |= 0x80
if y_direction >= 0:
# "bit 2" set if y does *not* scan negatively
expected |= 0x40
self._check_key("scanningMode", expected)
Original file line number Diff line number Diff line change
Expand Up @@ -63,14 +63,12 @@ def test__grid_points(self):

def test__scanmode(self):
grid_definition_template_0(self.test_cube, self.mock_grib)
self._check_key("iScansPositively", 1)
self._check_key("jScansPositively", 1)
self._check_scanmode(+1, +1)

def test__scanmode_reverse(self):
test_cube = self._make_test_cube(x_points=np.arange(7, 0, -1))
grid_definition_template_0(test_cube, self.mock_grib)
self._check_key("iScansPositively", 0)
self._check_key("jScansPositively", 1)
self._check_scanmode(-1, +1)


if __name__ == "__main__":
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,14 +85,12 @@ def test__grid_points(self):

def test__scanmode(self):
grid_definition_template_1(self.test_cube, self.mock_grib)
self._check_key("iScansPositively", 1)
self._check_key("jScansPositively", 1)
self._check_scanmode(+1, +1)

def test__scanmode_reverse(self):
test_cube = self._make_test_cube(x_points=np.arange(7, 0, -1))
grid_definition_template_1(test_cube, self.mock_grib)
self._check_key("iScansPositively", 0)
self._check_key("jScansPositively", 1)
self._check_scanmode(-1, +1)

def test__rotated_pole(self):
cs = RotatedGeogCS(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,16 +72,14 @@ def test__template_specifics(self):

def test__scanmode(self):
grid_definition_template_10(self.mercator_test_cube, self.mock_grib)
self._check_key("iScansPositively", 1)
self._check_key("jScansPositively", 1)
self._check_scanmode(+1, +1)

def test__scanmode_reverse(self):
test_cube = self._make_test_cube(
x_points=np.arange(7e6, 0, -1e6), coord_units="m"
)
grid_definition_template_10(test_cube, self.mock_grib)
self._check_key("iScansPositively", 0)
self._check_key("jScansPositively", 1)
self._check_scanmode(-1, +1)


if __name__ == "__main__":
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,14 +126,12 @@ def test__scale_factor(self):

def test__scanmode(self):
grid_definition_template_12(self.test_cube, self.mock_grib)
self._check_key("iScansPositively", 1)
self._check_key("jScansPositively", 1)
self._check_scanmode(+1, +1)

def test__scanmode_reverse(self):
test_cube = self._make_test_cube(x_points=np.arange(7, 0, -1))
grid_definition_template_12(test_cube, self.mock_grib)
self._check_key("iScansPositively", 0)
self._check_key("jScansPositively", 1)
self._check_scanmode(-1, +1)


if __name__ == "__main__":
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,14 +101,12 @@ def test__template_specifics(self):

def test__scanmode(self):
grid_definition_template(self.test_cube, self.mock_grib)
self._check_key("iScansPositively", 1)
self._check_key("jScansPositively", 1)
self._check_scanmode(+1, +1)

def test__scanmode_reverse(self):
test_cube = self._make_test_cube(x_points=np.arange(7e6, 0, -1e6))
grid_definition_template(test_cube, self.mock_grib)
self._check_key("iScansPositively", 0)
self._check_key("jScansPositively", 1)
self._check_scanmode(-1, +1)

def __fail_false_easting_northing(self, false_easting, false_northing):
cs = self._default_coord_system(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,16 +74,14 @@ def test__template_specifics(self):

def test__scanmode(self):
grid_definition_template_20(self.stereo_test_cube, self.mock_grib)
self._check_key("iScansPositively", 1)
self._check_key("jScansPositively", 1)
self._check_scanmode(+1, +1)

def test__scanmode_reverse(self):
stereo_test_cube = self._make_test_cube(
x_points=np.arange(7e6, 0, -1e6), coord_units="m"
)
grid_definition_template_20(stereo_test_cube, self.mock_grib)
self._check_key("iScansPositively", 0)
self._check_key("jScansPositively", 1)
self._check_scanmode(-1, +1)

def test_projection_centre(self):
grid_definition_template_20(self.stereo_test_cube, self.mock_grib)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,14 +103,12 @@ def test__template_specifics(self):

def test__scanmode(self):
grid_definition_template_30(self.test_cube, self.mock_grib)
self._check_key("iScansPositively", 1)
self._check_key("jScansPositively", 1)
self._check_scanmode(+1, +1)

def test__scanmode_reverse(self):
test_cube = self._make_test_cube(x_points=np.arange(7e6, 0, -1e6))
grid_definition_template_30(test_cube, self.mock_grib)
self._check_key("iScansPositively", 0)
self._check_key("jScansPositively", 1)
self._check_scanmode(-1, +1)

def test_projection_centre(self):
grid_definition_template_30(self.test_cube, self.mock_grib)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,14 +64,12 @@ def test__grid_points(self):

def test__scanmode(self):
grid_definition_template_4(self.test_cube, self.mock_grib)
self._check_key("iScansPositively", 1)
self._check_key("jScansPositively", 1)
self._check_scanmode(+1, +1)

def test__scanmode_reverse(self):
test_cube = self._make_test_cube(x_points=np.arange(7, 0, -1))
grid_definition_template_4(test_cube, self.mock_grib)
self._check_key("iScansPositively", 0)
self._check_key("jScansPositively", 1)
self._check_scanmode(-1, +1)


if __name__ == "__main__":
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,14 +78,12 @@ def test__grid_shape(self):

def test__scanmode(self):
grid_definition_template_5(self.test_cube, self.mock_grib)
self._check_key("iScansPositively", 1)
self._check_key("jScansPositively", 1)
self._check_scanmode(+1, +1)

def test__scanmode_reverse(self):
test_cube = self._make_test_cube(y_points=[5.0, 2.0])
grid_definition_template_5(test_cube, self.mock_grib)
self._check_key("iScansPositively", 1)
self._check_key("jScansPositively", 0)
self._check_scanmode(+1, -1)

def test__rotated_pole(self):
cs = RotatedGeogCS(
Expand Down
Loading