Skip to content

Commit

Permalink
Merge branch 'main' into dependabot/github_actions/dependencies-414c0…
Browse files Browse the repository at this point in the history
…58014
  • Loading branch information
pp-mo authored Jul 17, 2024
2 parents cb2ec21 + 1ba6eca commit 9703693
Show file tree
Hide file tree
Showing 25 changed files with 206 additions and 185 deletions.
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
87 changes: 46 additions & 41 deletions requirements/locks/py310-linux-64.lock

Large diffs are not rendered by default.

87 changes: 46 additions & 41 deletions requirements/locks/py311-linux-64.lock

Large diffs are not rendered by default.

87 changes: 46 additions & 41 deletions requirements/locks/py312-linux-64.lock

Large diffs are not rendered by default.

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)


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

0 comments on commit 9703693

Please sign in to comment.