예제 #1
0
 def test_time(self):
     """Test rejects cube without time coord"""
     cube = self.valid.copy()
     cube.remove_coord("time")
     msg = "Input cube has no time coordinate"
     with self.assertRaisesRegexp(InvalidCubeError, msg):
         check_input_coords(cube, require_time=True)
예제 #2
0
 def test_additional_nonscalar_dimension(self):
     """Test rejects cube with multiple realizations"""
     vel1 = self.valid.copy()
     vel1.add_aux_coord(DimCoord(1, standard_name="realization"))
     vel2 = self.valid.copy()
     vel2.add_aux_coord(DimCoord(2, standard_name="realization"))
     invalid_3d, = (iris.cube.CubeList([vel1, vel2])).merge()
     msg = "Cube has 3"
     with self.assertRaisesRegexp(InvalidCubeError, msg):
         check_input_coords(invalid_3d)
예제 #3
0
    def process(self, cube, timestep):
        """
        Extrapolates input cube data and updates validity time.  The input
        cube should have precisely two non-scalar dimension coordinates
        (spatial x/y), and is expected to be in a projection such that grid
        spacing is the same (or very close) at all points within the spatial
        domain.  The input cube should also have a "time" coordinate.

        Args:
            cube (iris.cube.Cube):
                The 2D cube containing data to be advected
            timestep (datetime.timedelta):
                Advection time step

        Returns:
            iris.cube.Cube:
                New cube with updated time and extrapolated data.  New data
                are filled with np.nan and masked where source data were
                out of bounds (ie where data could not be advected from outside
                the cube domain).

        """
        # check that the input cube has precisely two non-scalar dimension
        # coordinates (spatial x/y) and a scalar time coordinate
        check_input_coords(cube, require_time=True)

        # check spatial coordinates match those of plugin velocities
        if cube.coord(axis="x") != self.x_coord or cube.coord(axis="y") != self.y_coord:
            raise InvalidCubeError(
                "Input data grid does not match advection " "velocities"
            )

        # derive velocities in "grid squares per second"
        def grid_spacing(coord):
            """Calculate grid spacing along a given spatial axis"""
            new_coord = coord.copy()
            new_coord.convert_units("m")
            return np.float32(np.diff((new_coord).points)[0])

        grid_vel_x = self.vel_x.data / grid_spacing(cube.coord(axis="x"))
        grid_vel_y = self.vel_y.data / grid_spacing(cube.coord(axis="y"))

        # raise a warning if data contains unmasked NaNs
        nan_count = np.count_nonzero(~np.isfinite(cube.data))
        if nan_count > 0:
            warnings.warn("input data contains unmasked NaNs")

        # perform advection and create output cube
        advected_data = self._advect_field(
            cube.data, grid_vel_x, grid_vel_y, round(timestep.total_seconds())
        )
        advected_cube = self._create_output_cube(cube, advected_data, timestep)
        return advected_cube
예제 #4
0
    def __init__(self, vel_x, vel_y, metadata_dict=None):
        """
        Initialises the plugin.  Velocities are expected to be on a regular
        grid (such that grid spacing in metres is the same at all points in
        the domain).

        Args:
            vel_x (iris.cube.Cube):
                Cube containing a 2D array of velocities along the x
                coordinate axis
            vel_y (iris.cube.Cube):
                Cube containing a 2D array of velocities along the y
                coordinate axis

        Keyword Args:
            metadata_dict (dict):
                Dictionary containing information for amending the metadata
                of the output cube. Please see the
                :func:`improver.utilities.cube_metadata.amend_metadata`
                for information regarding the allowed contents of the metadata
                dictionary.
        """

        # check each input velocity cube has precisely two non-scalar
        # dimension coordinates (spatial x/y)
        check_input_coords(vel_x)
        check_input_coords(vel_y)

        # check input velocity cubes have the same spatial coordinates
        if (vel_x.coord(axis="x") != vel_y.coord(axis="x")
                or vel_x.coord(axis="y") != vel_y.coord(axis="y")):
            raise InvalidCubeError("Velocity cubes on unmatched grids")

        vel_x.convert_units('m s-1')
        vel_y.convert_units('m s-1')

        self.vel_x = vel_x
        self.vel_y = vel_y

        self.x_coord = vel_x.coord(axis="x")
        self.y_coord = vel_x.coord(axis="y")

        # Initialise metadata dictionary.
        if metadata_dict is None:
            metadata_dict = {}
        self.metadata_dict = metadata_dict
예제 #5
0
    def __init__(self,
                 vel_x: Cube,
                 vel_y: Cube,
                 attributes_dict: Optional[Dict] = None) -> None:
        """
        Initialises the plugin.  Velocities are expected to be on a regular
        grid (such that grid spacing in metres is the same at all points in
        the domain).

        Args:
            vel_x:
                Cube containing a 2D array of velocities along the x
                coordinate axis
            vel_y:
                Cube containing a 2D array of velocities along the y
                coordinate axis
            attributes_dict:
                Dictionary containing information for amending the attributes
                of the output cube.
        """

        # check each input velocity cube has precisely two non-scalar
        # dimension coordinates (spatial x/y)
        check_input_coords(vel_x)
        check_input_coords(vel_y)

        # check input velocity cubes have the same spatial coordinates
        if vel_x.coord(axis="x") != vel_y.coord(axis="x") or vel_x.coord(
                axis="y") != vel_y.coord(axis="y"):
            raise InvalidCubeError("Velocity cubes on unmatched grids")

        vel_x.convert_units("m s-1")
        vel_y.convert_units("m s-1")

        self.vel_x = vel_x
        self.vel_y = vel_y

        self.x_coord = vel_x.coord(axis="x")
        self.y_coord = vel_x.coord(axis="y")

        # Initialise metadata dictionary.
        if attributes_dict is None:
            attributes_dict = {}
        self.attributes_dict = attributes_dict
예제 #6
0
 def test_additional_scalar_dimension(self):
     """Test accepts cube with single realization coordinate"""
     vel = self.valid.copy()
     vel.add_aux_coord(DimCoord(1, standard_name="realization"))
     check_input_coords(vel)
예제 #7
0
 def test_missing_spatial_dimension(self):
     """Test rejects cube missing y axis"""
     invalid_1d = self.valid[0]
     invalid_1d.remove_coord("projection_y_coordinate")
     with self.assertRaises(InvalidCubeError):
         check_input_coords(invalid_1d)
예제 #8
0
    def process(self, cube, timestep):
        """
        Extrapolates input cube data and updates validity time.  The input
        cube should have precisely two non-scalar dimension coordinates
        (spatial x/y), and is expected to be in a projection such that grid
        spacing is the same (or very close) at all points within the spatial
        domain.  The input cube should also have a "time" coordinate.

        Args:
            cube (iris.cube.Cube):
                The 2D cube containing data to be advected
            timestep (datetime.timedelta):
                Advection time step

        Returns:
            iris.cube.Cube:
                New cube with updated time and extrapolated data.  New data
                are filled with np.nan and masked where source data were
                out of bounds (ie where data could not be advected from outside
                the cube domain).

        """
        # check that the input cube has precisely two non-scalar dimension
        # coordinates (spatial x/y) and a scalar time coordinate
        check_input_coords(cube, require_time=True)

        # check spatial coordinates match those of plugin velocities
        if (cube.coord(axis="x") != self.x_coord
                or cube.coord(axis="y") != self.y_coord):
            raise InvalidCubeError("Input data grid does not match advection "
                                   "velocities")

        # derive velocities in "grid squares per second"
        def grid_spacing(coord):
            """Calculate grid spacing along a given spatial axis"""
            new_coord = coord.copy()
            new_coord.convert_units('m')
            return np.float32(np.diff((new_coord).points)[0])

        grid_vel_x = self.vel_x.data / grid_spacing(cube.coord(axis="x"))
        grid_vel_y = self.vel_y.data / grid_spacing(cube.coord(axis="y"))

        # raise a warning if data contains unmasked NaNs
        nan_count = np.count_nonzero(~np.isfinite(cube.data))
        if nan_count > 0:
            warnings.warn("input data contains unmasked NaNs")

        # perform advection and create output cube
        advected_data = self._advect_field(cube.data, grid_vel_x, grid_vel_y,
                                           timestep.total_seconds())
        advected_cube = cube.copy(data=advected_data)

        # increment output cube time and add a "forecast_period" coordinate
        original_datetime, = \
            (cube.coord("time").units).num2date(cube.coord("time").points)
        new_datetime = original_datetime + timestep

        new_time = (cube.coord("time").units).date2num(new_datetime)

        advected_cube.coord("time").points = new_time
        advected_cube.coord("time").convert_units(
            "seconds since 1970-01-01 00:00:00")
        advected_cube.coord("time").points = (np.around(
            advected_cube.coord("time").points).astype(np.int64))

        try:
            advected_cube.coord("forecast_reference_time").convert_units(
                "seconds since 1970-01-01 00:00:00")
        except CoordinateNotFoundError:
            frt_coord = cube.coord("time").copy()
            frt_coord.rename("forecast_reference_time")
            advected_cube.add_aux_coord(frt_coord)
            advected_cube.coord("forecast_reference_time").convert_units(
                "seconds since 1970-01-01 00:00:00")

        frt_points = np.around(
            advected_cube.coord("forecast_reference_time").points).astype(
                np.int64)
        advected_cube.coord("forecast_reference_time").points = frt_points

        forecast_period_seconds = np.int32(timestep.total_seconds())
        forecast_period_coord = AuxCoord(forecast_period_seconds,
                                         standard_name="forecast_period",
                                         units="s")
        try:
            advected_cube.remove_coord("forecast_period")
        except CoordinateNotFoundError:
            pass
        advected_cube.add_aux_coord(forecast_period_coord)

        # Modify the source attribute to describe the advected field as a
        # Nowcast
        if "institution" in advected_cube.attributes.keys():
            advected_cube.attributes["source"] = ("{} Nowcast".format(
                advected_cube.attributes["institution"]))
        else:
            advected_cube.attributes["source"] = "Nowcast"
        amend_attributes(advected_cube, self.attributes_dict)
        set_history_attribute(advected_cube, "Nowcast")
        return advected_cube
 def test_time(self):
     """Test rejects cube without time coord"""
     msg = "Input cube has no time coordinate"
     with self.assertRaisesRegexp(InvalidCubeError, msg):
         check_input_coords(self.valid, require_time=True)