def test_process(with_percentiles, input_as_cube, input_has_time_bounds): """Checks that the plugin process method returns a cube with expected data and time coord for our test data""" data_shape = [3, 4] data = np.array( [np.zeros(data_shape, dtype=np.float32), np.ones(data_shape, dtype=np.float32)] ) expected_data = [[0, 0, 0, 0], [0, 0, 0, 0], [1, 1, 1, 1]] if with_percentiles: cube = make_percentile_cube(data_shape, time_bounds=input_has_time_bounds) data = np.array([data, data, data]) expected_data = np.array([expected_data, expected_data, expected_data]) else: cube = make_input_cube(data_shape, time_bounds=input_has_time_bounds) cube.data = data local_time = datetime(2017, 11, 10, 5, 0) timezone_cube = make_timezone_cube() row1 = [cube.coord("time").units.date2num(datetime(2017, 11, 10, 4, 0))] * 4 row2 = [cube.coord("time").units.date2num(datetime(2017, 11, 10, 5, 0))] * 4 row3 = [cube.coord("time").units.date2num(datetime(2017, 11, 10, 6, 0))] * 4 expected_times = [row1, row2, row3] expected_bounds = np.array(expected_times).reshape((3, 4, 1)) + [[[-3600, 0]]] if not input_as_cube: # Split cube into a list of cubes cube = [c for c in cube.slices_over("time")] result = TimezoneExtraction()(cube, timezone_cube, local_time) assert_metadata_ok(result) assert np.isclose(result.data, expected_data).all() assert np.isclose(result.coord("time").points, expected_times).all() if input_has_time_bounds: assert np.isclose(result.coord("time").bounds, expected_bounds).all() else: assert result.coord("time").bounds is None
def process( timezone_cube: cli.inputcube, local_time: str, *cubes: cli.inputcube, ): """Calculates timezone-offset data for the specified UTC output times Args: timezone_cube (iris.cube.Cube): Cube describing the UTC offset for the local time at each grid location. Must have the same spatial coords as input_cube. Use generate-timezone-mask-ancillary to create this. local_time (str): The "local" time of the output cube as %Y%m%dT%H%M. This will form a scalar "time_in_local_timezone" coord on the output cube, while the "time" coord will be auxillary to the spatial coords and will show the UTC time that matches the local_time at each point. cubes (list of iris.cube.Cube): Source data to be remapped onto time-zones. Must contain an exact 1-to-1 mapping of times to time-zones. Multiple input files will be merged into one cube. Returns: iris.cube.Cube: Processed cube. """ from datetime import datetime from improver.utilities.temporal import TimezoneExtraction local_datetime = datetime.strptime(local_time, "%Y%m%dT%H%M") return TimezoneExtraction()(cubes, timezone_cube, local_datetime)
def test_check_timezones_are_unique_fail(offset): """Checks that check_timezones_are_unique fails if we break our test cube""" timezone_cube = make_timezone_cube() timezone_cube.data[0, 0, 0] += offset with pytest.raises( ValueError, match=r"Timezone cube does not map exactly one time zone to each spatial point", ): TimezoneExtraction().check_timezones_are_unique(timezone_cube)
def test_bad_dtype(): """Checks that the plugin raises a useful error if the output are float64""" cube = make_input_cube([3, 4]) local_time = datetime(2017, 11, 10, 5, 0) timezone_cube = make_timezone_cube() timezone_cube.data = timezone_cube.data.astype(np.int32) with pytest.raises( TypeError, match=r"Operation multiply on types \{dtype\(\'.*32\'\), dtype\(\'.*32\'\)\} results in", ): TimezoneExtraction()(cube, timezone_cube, local_time)
def test_create_output_cube(with_cell_method): """Tests that the create_output_cube method builds a cube with appropriate meta-data. The Time coord is tested in test_process as it depends on multiple methods.""" data_shape = [3, 4] cube = make_input_cube(data_shape) if with_cell_method: cell_method = CellMethod("minimum", coords="time") cube.add_cell_method(cell_method) local_time = datetime(2017, 11, 9, 12, 0) plugin = TimezoneExtraction() plugin.output_data = np.zeros(data_shape, dtype=np.float32) plugin.time_points = np.full( data_shape, fill_value=Unit(TIME_COORDS["time"].units).date2num( datetime(2017, 11, 10, 4, 0)), dtype=np.int64, ) plugin.time_bounds = None result = plugin.create_output_cube(cube, local_time) assert_metadata_ok(result) assert result.name() == cube.name() assert result.units == cube.units result_local_time = result.coord("time_in_local_timezone") assert [cell.point for cell in result_local_time.cells()] == [local_time] expected_shape = data_shape assert result.shape == tuple(expected_shape) assert result.attributes == cube.attributes if with_cell_method: assert result.cell_methods == tuple([cell_method])
def test_check_input_cube_time(local_time, expect_success): """Checks that check_input_cube_time can differentiate between arguments that match expected times and arguments that don't.""" cube = make_input_cube([3, 4]) timezone_cube = make_timezone_cube() plugin = TimezoneExtraction() plugin.check_input_cube_dims(cube, timezone_cube) if expect_success: plugin.check_input_cube_time(cube, local_time) else: with pytest.raises( ValueError, match=r"Time coord on input cube does not match required times." ): plugin.check_input_cube_time(cube, local_time)
def test_bad_spatial_coords(): """Checks that the plugin raises a useful error if the longitude coord is shifted by 180 degrees""" cube = make_input_cube([3, 4]) local_time = datetime(2017, 11, 10, 5, 0) timezone_cube = make_timezone_cube() timezone_cube.data = timezone_cube.data.astype(np.int32) longitude_coord = timezone_cube.coord("longitude") timezone_cube.replace_coord(longitude_coord.copy(longitude_coord.points + 180)) with pytest.raises( ValueError, match=r"Spatial coordinates on input_cube and timezone_cube do not match.", ): TimezoneExtraction()(cube, timezone_cube, local_time)
def test_check_input_cube_dims(include_time_coord): """Checks that check_input_cube_dims can differentiate between an input cube with time, y, x coords and one where time is missing. Also checks that timezone_cube has been reordered correctly.""" cube = make_input_cube([3, 4]) timezone_cube = make_timezone_cube() plugin = TimezoneExtraction() if include_time_coord: plugin.check_input_cube_dims(cube, timezone_cube) assert plugin.timezone_cube.coord_dims("UTC_offset") == tuple( [plugin.timezone_cube.ndim - 1]) else: cube.remove_coord("time") with pytest.raises( ValueError, match=r"Expected coords on input_cube: time, y, x "): plugin.check_input_cube_dims(cube, timezone_cube)
def process( local_time: str, *cubes: cli.inputcube, ): """Calculates timezone-offset data for the specified UTC output times Args: local_time (str): The "local" time of the output cube as %Y%m%dT%H%M. This will form a scalar "time_in_local_timezone" coord on the output cube, while the "time" coord will be auxillary to the spatial coords and will show the UTC time that matches the local_time at each point. This can also be provided in the form of a filepath where the 'local_time' is denoted in this format at the beginning of the basename. cubes (list of iris.cube.Cube): Source data to be remapped onto time-zones. Must contain an exact 1-to-1 mapping of times to time-zones. Multiple input files will be merged into one cube. Assumes the final argument is a timezone_cube, which is a cube describing the UTC offset for the local time at each grid location. Must have the same spatial coords as input_cube. Use generate-timezone-mask-ancillary to create this. Returns: iris.cube.Cube: Processed cube. """ import os from datetime import datetime from improver.utilities.temporal import TimezoneExtraction timezone_cube = cubes[-1] cubes = cubes[:-1] local_time = os.path.basename(local_time)[:13] local_datetime = datetime.strptime(local_time, "%Y%m%dT%H%M") return TimezoneExtraction()(cubes, timezone_cube, local_datetime)
def test_check_timezones_are_unique_pass(): """Checks that check_timezones_are_unique allows our test cube""" timezone_cube = make_timezone_cube() TimezoneExtraction().check_timezones_are_unique(timezone_cube)