Exemplo n.º 1
0
 def test_float64_cube_data_with_fix(self):
     """Test a cube with 64 bit data is converted to 32 bit data."""
     self.cube.data = self.cube.data.astype(np.float64)
     expected_cube = self.cube.copy()
     expected_cube.data = expected_cube.data.astype(np.float64)
     check_cube_not_float64(self.cube, fix=True)
     self.assertEqual(self.cube, expected_cube)
Exemplo n.º 2
0
 def test_float64_cube_forecast_ref_time_coord_points_ok(self):
     """Test a pass of a cube with 64 bit fcast ref time coord points."""
     frt_coord = iris.coords.AuxCoord(
         [np.float64(min(self.cube.coord("time").points))],
         standard_name="forecast_reference_time")
     self.cube.add_aux_coord(frt_coord)
     check_cube_not_float64(self.cube)
Exemplo n.º 3
0
def save_netcdf(cubelist, filename):
    """Save the input Cube or CubeList as a NetCDF file.

    Uses the functionality provided by iris.fileformats.netcdf.save with
    local_keys to record non-global attributes as data attributes rather than
    global attributes.

    Args:
        cubelist (iris.cube.Cube or iris.cube.CubeList):
            Cube or list of cubes to be saved
        filename (str):
            Filename to save input cube(s)
    """
    if isinstance(cubelist, iris.cube.Cube):
        cubelist = [cubelist]

    for cube in cubelist:
        check_cube_not_float64(cube)
        order_cell_methods(cube)

    global_keys = ['title', 'um_version', 'grid_id', 'source', 'Conventions',
                   'mosg__grid_type', 'mosg__model_configuration',
                   'mosg__grid_domain', 'mosg__grid_version',
                   'institution', 'history', 'bald__isPrefixedBy']
    local_keys = {key for cube in cubelist
                  for key in cube.attributes.keys()
                  if key not in global_keys}

    cubelist = append_metadata_cube(cubelist, global_keys)

    iris.fileformats.netcdf.save(cubelist, filename, local_keys=local_keys)
Exemplo n.º 4
0
 def test_float64_cube_coord_points(self):
     """Test a failure of a cube with 64 bit coord points."""
     self.cube.coord("projection_x_coordinate").points = (
         self.cube.coord("projection_x_coordinate").points.astype(
             np.float64))
     msg = "64 bit coord points not allowed"
     with self.assertRaisesRegex(TypeError, msg):
         check_cube_not_float64(self.cube)
Exemplo n.º 5
0
 def test_float64_cube_coord_bounds(self):
     """Test a failure of a cube with 64 bit coord bounds."""
     x_coord = self.cube.coord("projection_x_coordinate")
     # Default np.array for float input is np.float64.
     x_coord.bounds = (np.array([(point - 10., point + 10.)
                                 for point in x_coord.points]))
     msg = "64 bit coord bounds not allowed"
     with self.assertRaisesRegex(TypeError, msg):
         check_cube_not_float64(self.cube)
Exemplo n.º 6
0
def save_netcdf(cubelist, filename):
    """Save the input Cube or CubeList as a NetCDF file.

    Uses the functionality provided by iris.fileformats.netcdf.save with
    local_keys to record non-global attributes as data attributes rather than
    global attributes.

    Args:
        cubelist (iris.cube.Cube or iris.cube.CubeList):
            Cube or list of cubes to be saved
        filename (str):
            Filename to save input cube(s)

    Raises:
        warning if cubelist contains cubes of varying dimensions.
    """
    if isinstance(cubelist, iris.cube.Cube):
        cubelist = [cubelist]

    chunksizes = None
    xy_chunksizes = [None, None]
    for cube in cubelist:
        check_cube_not_float64(cube)
        _order_cell_methods(cube)
        _check_for_units(cube)
    # If all xy slices are the same shape, use this to determine
    # the chunksize for the netCDF (eg. 1, 1, 970, 1042)
    chunksizes = None
    if len(set([cube.shape[:2] for cube in cubelist])) == 1:
        cube = cubelist[0]
        if cube.ndim >= 2:
            xy_chunksizes = [cube.shape[-2], cube.shape[-1]]
            chunksizes = tuple([1] * (cube.ndim - 2) + xy_chunksizes)
    else:
        msg = ("Chunksize not set as cubelist "
               "contains cubes of varying dimensions")
        warnings.warn(msg)

    global_keys = [
        'title', 'um_version', 'grid_id', 'source', 'Conventions',
        'mosg__grid_type', 'mosg__model_configuration', 'mosg__grid_domain',
        'mosg__grid_version', 'institution', 'history', 'bald__isPrefixedBy'
    ]
    local_keys = {
        key
        for cube in cubelist for key in cube.attributes.keys()
        if key not in global_keys
    }

    cubelist = _append_metadata_cube(cubelist, global_keys)
    iris.fileformats.netcdf.save(cubelist,
                                 filename,
                                 local_keys=local_keys,
                                 complevel=1,
                                 shuffle=True,
                                 zlib=True,
                                 chunksizes=chunksizes)
Exemplo n.º 7
0
    def process(self, input_cube):
        """Adjust the 4d wind field - cube - (x, y, z including times).

        Args:
            input_cube (iris.cube.Cube):
                The wind cube to be operated upon. Should be wind speed on
                height_levels for all desired forecast times.

        Returns:
            output_cube (iris.cube.Cube):
                The 4d wind field with roughness and height correction
                applied in the same order as the input cube.

        Raises
        ------
        TypeError: If input_cube is not a cube.

        """
        if not isinstance(input_cube, iris.cube.Cube):
            msg = "wind input is not a cube, but {}"
            raise TypeError(msg.format(type(input_cube)))
        check_cube_not_float64(input_cube, fix=True)
        (self.x_name, self.y_name, self.z_name,
         self.t_name) = self.find_coord_names(input_cube)
        xwp, ywp, zwp, twp = self.find_coord_order(input_cube)
        if np.isnan(twp):
            input_cube.transpose([ywp, xwp, zwp])
        else:
            input_cube.transpose([ywp, xwp, zwp, twp])  # problems with slices
        rchc_list = iris.cube.CubeList()
        if self.z_0 is None:
            z0_data = None
        else:
            z0_data = self.z_0.data
        roughness_correction = RoughnessCorrectionUtilities(
            self.a_over_s.data, self.sigma.data, z0_data, self.pp_oro.data,
            self.model_oro.data, self.ppres, self.modres)
        self.check_wind_ancil(xwp, ywp)
        hld = self.find_heightgrid(input_cube)
        for time_slice in input_cube.slices_over("time"):
            if np.isnan(time_slice.data).any() or (time_slice.data < 0.).any():
                msg = ('{} has invalid wind data')
                raise ValueError(msg.format(time_slice.coord(self.t_name)))
            rc_hc = copy.deepcopy(time_slice)
            rc_hc.data = roughness_correction.do_rc_hc_all(
                hld, time_slice.data)
            rchc_list.append(rc_hc)
        output_cube = rchc_list.merge_cube()
        # reorder input_cube and output_cube as original
        if np.isnan(twp):
            input_cube.transpose(np.argsort([ywp, xwp, zwp]))
            output_cube.transpose(np.argsort([ywp, xwp, zwp]))
        else:
            input_cube.transpose(np.argsort([ywp, xwp, zwp, twp]))
            output_cube.transpose(np.argsort([twp, ywp, xwp, zwp]))
        return output_cube
Exemplo n.º 8
0
 def test_float64_cube_coord_points_with_fix(self):
     """Test a failure of a cube with 64 bit coord points."""
     self.cube.coord("projection_x_coordinate").points = (
         self.cube.coord("projection_x_coordinate").points.astype(
             np.float64))
     expected_cube = self.cube.copy()
     expected_cube.coord("projection_x_coordinate").points = (
         expected_cube.coord("projection_x_coordinate").points.astype(
             np.float64))
     expected_coord = expected_cube.coord("projection_x_coordinate")
     check_cube_not_float64(self.cube, fix=True)
     self.assertEqual(self.cube, expected_cube)
     self.assertEqual(self.cube.coord("projection_x_coordinate"),
                      expected_coord)
Exemplo n.º 9
0
 def test_float64_cube_coord_bounds_with_fix(self):
     """Test a failure of a cube with 64 bit coord bounds."""
     x_coord = self.cube.coord("projection_x_coordinate")
     # Default np.array for float input is np.float64.
     x_coord.bounds = (np.array([(point - 10., point + 10.)
                                 for point in x_coord.points]))
     expected_cube = self.cube.copy()
     expected_cube.coord("projection_x_coordinate").points = (
         expected_cube.coord("projection_x_coordinate").points.astype(
             np.float64))
     expected_coord = expected_cube.coord("projection_x_coordinate")
     check_cube_not_float64(self.cube, fix=True)
     self.assertEqual(self.cube, expected_cube)
     self.assertEqual(self.cube.coord("projection_x_coordinate"),
                      expected_coord)
Exemplo n.º 10
0
def main(argv=None):
    """Extend radar mask based on coverage data."""
    parser = ArgParser(description="Extend radar mask based on coverage "
                       "data.")
    parser.add_argument("radar_data_filepath",
                        metavar="RADAR_DATA_FILEPATH",
                        type=str,
                        help="Full path to input NetCDF file "
                        "containing the radar variable to remask.")
    parser.add_argument("coverage_filepath",
                        metavar="COVERAGE_FILEPATH",
                        type=str,
                        help="Full path to input NetCDF file "
                        "containing radar coverage data.")
    parser.add_argument("output_filepath",
                        metavar="OUTPUT_FILEPATH",
                        type=str,
                        help="Full path to save remasked radar data "
                        "NetCDF file.")
    parser.add_argument("--fix_float64",
                        action='store_true',
                        default=False,
                        help="Check and fix cube for float64 data. Without "
                        "this option an exception will be raised if "
                        "float64 data is found but no fix applied.")

    args = parser.parse_args(args=argv)

    # load data
    radar_data = load_cube(args.radar_data_filepath)
    coverage = load_cube(args.coverage_filepath)

    # extend mask
    remasked_data = ExtendRadarMask().process(radar_data, coverage)

    # Check and fix for float64 data only option:
    check_cube_not_float64(remasked_data, fix=args.fix_float64)

    # save output file
    save_netcdf(remasked_data, args.output_filepath)
Exemplo n.º 11
0
def process(coverage, radar_data, fix_float64=False):
    """ Extend radar mask based on coverage data.

    Extends the mask on radar data based on the radar coverage composite.
    Update the mask on the input cube to reflect where coverage is valid.

    Args:
        coverage (iris.cube.Cube):
            Cube containing the radar data to remask
        radar_data (iris.cube.Cube):
            Cube containing the radar coverage data.
        fix_float64 (bool):
            Check and fix cube for float64 data. Without this, an exception
            will be raised if float64 data is found but no fix applied.

    Returns:
        result (iris.cube.Cube):
            A cube with the remasked radar data.
    """
    # extend mask
    result = ExtendRadarMask().process(radar_data, coverage)
    # Check and fix for float64 data only option:
    check_cube_not_float64(result, fix=fix_float64)
    return result
Exemplo n.º 12
0
def set_up_variable_cube(data,
                         name='air_temperature',
                         units='K',
                         spatial_grid='latlon',
                         time=datetime(2017, 11, 10, 4, 0),
                         time_bounds=None,
                         frt=datetime(2017, 11, 10, 0, 0),
                         realizations=None,
                         include_scalar_coords=None,
                         attributes=None,
                         standard_grid_metadata=None):
    """
    Set up a cube containing a single variable field with:
    - x/y spatial dimensions (equal area or lat / lon)
    - optional leading "realization" dimension
    - "time", "forecast_reference_time" and "forecast_period" scalar coords
    - option to specify additional scalar coordinates
    - configurable attributes

    Args:
        data (np.ndarray):
            2D (y-x ordered) or 3D (realization-y-x ordered) array of data
            to put into the cube.

    Kwargs:
        name (str):
            Variable name (standard / long)
        units (str):
            Variable units
        spatial_grid (str):
            What type of x/y coordinate values to use.  Permitted values are
            "latlon" or "equalarea".
        time (datetime.datetime):
            Single cube validity time
        time_bounds (tuple or list of datetime.datetime instances):
            Lower and upper bound on time point, if required
        frt (datetime.datetime):
            Single cube forecast reference time
        realizations (list):
            List of forecast realizations.  If not present, taken from the
            leading dimension of the input data array (if 3D).
        include_scalar_coords (list):
            List of iris.coords.DimCoord or AuxCoord instances of length 1.
        attributes (dict):
            Optional cube attributes.
        standard_grid_metadata (str):
            Recognised mosg__model_configuration for which to set up Met
            Office standard grid attributes.  Should be 'uk_det', 'uk_ens',
            'gl_det' or 'gl_ens'.
    """
    # construct spatial dimension coordimates
    ypoints = data.shape[-2]
    xpoints = data.shape[-1]
    y_coord, x_coord = construct_xy_coords(ypoints, xpoints, spatial_grid)

    # construct realization dimension for 3D data, and dim_coords list
    ndims = len(data.shape)
    if ndims == 3:
        if realizations is not None:
            if len(realizations) != data.shape[0]:
                raise ValueError(
                    'Cannot generate {} realizations from data of shape '
                    '{}'.format(len(realizations), data.shape))
        else:
            realizations = np.arange(data.shape[0]).astype(np.int32)
        realization_coord = DimCoord(realizations, "realization", units="1")
        dim_coords = [(realization_coord, 0), (y_coord, 1), (x_coord, 2)]
    elif ndims == 2:
        dim_coords = [(y_coord, 0), (x_coord, 1)]
    else:
        raise ValueError(
            'Expected 2 or 3 dimensions on input data: got {}'.format(ndims))

    # construct list of aux_coords_and_dims
    scalar_coords = construct_scalar_time_coords(time, time_bounds, frt)
    if include_scalar_coords is not None:
        for coord in include_scalar_coords:
            scalar_coords.append((coord, None))

    # set up attributes
    cube_attrs = {}
    if standard_grid_metadata is not None:
        cube_attrs.update(MOSG_GRID_DEFINITION[standard_grid_metadata])
    if attributes is not None:
        cube_attrs.update(attributes)

    # create data cube
    try:
        cube = iris.cube.Cube(data,
                              name,
                              units=units,
                              dim_coords_and_dims=dim_coords,
                              aux_coords_and_dims=scalar_coords,
                              attributes=cube_attrs)
    except ValueError:
        # if "name" is not a standard name, set long name instead
        cube = iris.cube.Cube(data,
                              long_name=name,
                              units=units,
                              dim_coords_and_dims=dim_coords,
                              aux_coords_and_dims=scalar_coords,
                              attributes=cube_attrs)

    # don't allow unit tests to set up invalid cubes
    check_cube_not_float64(cube)

    return cube
Exemplo n.º 13
0
    def process(self, temperature_cube, orography_cube, land_sea_mask_cube):
        """Calculates the lapse rate from the temperature and orography cubes.

        Args:
            temperature_cube (iris.cube.Cube):
                Cube of air temperatures (K).

            orography_cube (Iris.cube.Cube):
                Cube containing orography data (metres)

            land_sea_mask_cube (iris.cube.Cube):
                Cube containing a binary land-sea mask. True for land-points
                and False for Sea.

        Returns:
            lapse_rate_cube (iris.cube.Cube):
                Cube containing lapse rate (Km-1)

        Raises
        ------
        TypeError: If input cubes are not cubes
        ValueError: If input cubes are the wrong units.

        """

        if not isinstance(temperature_cube, iris.cube.Cube):
            msg = "Temperature input is not a cube, but {}"
            raise TypeError(msg.format(type(temperature_cube)))

        if not isinstance(orography_cube, iris.cube.Cube):
            msg = "Orography input is not a cube, but {}"
            raise TypeError(msg.format(type(orography_cube)))

        if not isinstance(land_sea_mask_cube, iris.cube.Cube):
            msg = "Land/Sea mask input is not a cube, but {}"
            raise TypeError(msg.format(type(land_sea_mask_cube)))

        # Converts cube units.
        temperature_cube.convert_units('K')
        orography_cube.convert_units('metres')

        check_cube_not_float64(temperature_cube, fix=True)

        # Extract x/y co-ordinates.
        x_coord = temperature_cube.coord(axis='x').name()
        y_coord = temperature_cube.coord(axis='y').name()

        # Extract orography and land/sea mask data.
        orography_data = next(orography_cube.slices([y_coord, x_coord])).data
        land_sea_mask = next(land_sea_mask_cube.slices([y_coord,
                                                        x_coord])).data
        # Fill sea points with NaN values.
        orography_data = np.where(land_sea_mask, orography_data, np.nan)

        # Extract data array dimensions to define output arrays.
        dataarray_shape = next(temperature_cube.slices([y_coord,
                                                        x_coord])).shape
        dataarray_size = dataarray_shape[0] * dataarray_shape[1]

        # Array containing all of the subsections extracted from data array.
        # Also enforce single precision to speed up calculations.
        all_temp_subsections = np.zeros(
            (dataarray_size, self.nbhoodarray_size), dtype=np.float32)
        all_orog_subsections = np.zeros(
            (dataarray_size, self.nbhoodarray_size), dtype=np.float32)

        # Attempts to extract realizations. If cube doesn't contain the
        # dimension then place within list.
        try:
            slices_over_realization = temperature_cube.slices_over(
                "realization")
        except iris.exceptions.CoordinateNotFoundError:
            slices_over_realization = [temperature_cube]

        # Creates cube list to hold lapse rate data.
        lapse_rate_cube_list = iris.cube.CubeList([])

        for temp_slice in slices_over_realization:

            # Create slice to store lapse rate values.
            lapse_rate_slice = temp_slice

            temperature_data = temp_slice.data

            # Fill sea points with NaN values. Can't use Numpy mask since not
            # recognised by "generic_filter" function.
            temperature_data = np.where(land_sea_mask, temperature_data,
                                        np.nan)

            # Saves all neighbourhoods into "all_temp_subsections".
            # cval is value given to points outside the array.
            fnc = SaveNeighbourhood(allbuffers=all_temp_subsections)
            generic_filter(temperature_data,
                           fnc.filter,
                           size=self.nbhood_size,
                           mode='constant',
                           cval=np.nan)

            fnc = SaveNeighbourhood(allbuffers=all_orog_subsections)
            generic_filter(orography_data,
                           fnc.filter,
                           size=self.nbhood_size,
                           mode='constant',
                           cval=np.nan)

            # height_diff_mask is True for points where the height
            # difference between the central point and its neighbours
            # is > max_height_diff.
            height_diff_mask = self._create_heightdiff_mask(
                all_orog_subsections)

            # Mask points with extreme height differences as NaN.
            all_orog_subsections = np.where(height_diff_mask, np.nan,
                                            all_orog_subsections)
            all_temp_subsections = np.where(height_diff_mask, np.nan,
                                            all_temp_subsections)

            # Loop through both arrays and find gradient of each subsection.
            # The gradient indicates lapse rate - save into another array.
            # TODO: This for loop is the bottleneck in the code and needs to
            # be parallelised.
            lapse_rate_array = [
                self._calc_lapse_rate(temp, orog) for temp, orog in zip(
                    all_temp_subsections, all_orog_subsections)
            ]

            lapse_rate_array = np.array(
                lapse_rate_array, dtype=np.float32).reshape(dataarray_shape)

            # Enforces upper and lower limits on lapse rate values.
            lapse_rate_array = np.where(lapse_rate_array < self.min_lapse_rate,
                                        self.min_lapse_rate, lapse_rate_array)
            lapse_rate_array = np.where(lapse_rate_array > self.max_lapse_rate,
                                        self.max_lapse_rate, lapse_rate_array)

            lapse_rate_slice.data = lapse_rate_array
            lapse_rate_cube_list.append(lapse_rate_slice)

        lapse_rate_cube = lapse_rate_cube_list.merge_cube()
        lapse_rate_cube.rename('air_temperature_lapse_rate')
        lapse_rate_cube.units = 'K m-1'

        return lapse_rate_cube
Exemplo n.º 14
0
    def process(self, cube_ens_wdir):
        """Create a cube containing the wind direction averaged over the
        ensemble realizations.

        Args:
            cube_ens_wdir (iris.cube.Cube):
                Cube containing wind direction from multiple ensemble
                realizations.

        Returns:
            cube_mean_wdir (iris.cube.Cube):
                Cube containing the wind direction averaged from the
                ensemble realizations.
            cube_r_vals (np.ndarray):
                3D array - Radius taken from average complex wind direction
                angle.
            cube_confidence_measure (np.ndarray):
                3D array - The average distance from mean normalised - used
                as a confidence value.

        Raises
        ------
        TypeError: If cube_wdir is not a cube.

        """

        if not isinstance(cube_ens_wdir, iris.cube.Cube):
            msg = "Wind direction input is not a cube, but {}"
            raise TypeError(msg.format(type(cube_ens_wdir)))

        try:
            cube_ens_wdir.convert_units("degrees")
        except ValueError as err:
            msg = "Input cube cannot be converted to degrees: {}".format(err)
            raise ValueError(msg)

        # Demote input cube data and coords to float32 if float64
        check_cube_not_float64(cube_ens_wdir, fix=True)

        self.n_realizations = len(cube_ens_wdir.coord('realization').points)
        y_coord_name = cube_ens_wdir.coord(axis="y").name()
        x_coord_name = cube_ens_wdir.coord(axis="x").name()
        for wdir_slice in cube_ens_wdir.slices(
            ["realization", y_coord_name, x_coord_name]):
            self._reset()
            # Extract wind direction data.
            self.wdir_complex = self.deg_to_complex(wdir_slice.data)
            self.realization_axis, = wdir_slice.coord_dims("realization")

            # Copies input cube and remove realization dimension to create
            # cubes for storing results.
            self.wdir_slice_mean = next(wdir_slice.slices_over("realization"))
            self.wdir_slice_mean.remove_coord("realization")

            # Derive average wind direction.
            self.calc_wind_dir_mean()

            # Find radius values for wind direction average.
            self.find_r_values()

            # Calculate the confidence measure based on the difference
            # between the complex average and the individual ensemble
            # realizations.
            self.calc_confidence_measure()

            # Finds any meaningless averages and substitute with
            # the wind direction taken from the first ensemble realization.
            # Mask True if r values below threshold.
            where_low_r = np.where(self.r_vals_slice.data < self.r_thresh,
                                   True, False)
            # If the any point in the array contains poor r-values,
            # trigger decider function.
            if where_low_r.any():
                self.wind_dir_decider(where_low_r, wdir_slice)

            # Append to cubelists.
            self.wdir_cube_list.append(self.wdir_slice_mean)
            self.r_vals_cube_list.append(self.r_vals_slice)
            self.confidence_measure_cube_list.append(self.confidence_slice)

        # Combine cubelists into cube.
        cube_mean_wdir = self.wdir_cube_list.merge_cube()
        cube_r_vals = self.r_vals_cube_list.merge_cube()
        cube_confidence_measure = (
            self.confidence_measure_cube_list.merge_cube())

        # Check that the dimensionality of coordinates of the output cube
        # matches the input cube.
        first_slice = next(cube_ens_wdir.slices_over(["realization"]))
        cube_mean_wdir = check_cube_coordinates(first_slice, cube_mean_wdir)

        # Change cube identifiers.
        cube_mean_wdir.add_cell_method(CellMethod("mean",
                                                  coords="realization"))
        cube_r_vals.long_name = "radius_of_complex_average_wind_from_direction"
        cube_r_vals.units = None
        cube_confidence_measure.long_name = (
            "confidence_measure_of_wind_from_direction")
        cube_confidence_measure.units = None

        return cube_mean_wdir, cube_r_vals, cube_confidence_measure
Exemplo n.º 15
0
 def test_float64_cube_data(self):
     """Test a failure of a cube with 64 bit data."""
     self.cube.data = self.cube.data.astype(np.float64)
     msg = "64 bit cube not allowed"
     with self.assertRaisesRegex(TypeError, msg):
         check_cube_not_float64(self.cube)
Exemplo n.º 16
0
 def test_float32_ok(self):
     """Test a cube that should pass."""
     check_cube_not_float64(self.cube)
Exemplo n.º 17
0
    def __init__(self,
                 a_over_s_cube,
                 sigma_cube,
                 pporo_cube,
                 modoro_cube,
                 modres,
                 z0_cube=None,
                 height_levels_cube=None):
        """Initialise the RoughnessCorrection instance.

        Args:
            a_over_s_cube (iris.cube.Cube):
                2D - model silhouette roughness on pp grid. dimensionless
            sigma_cube (iris.cube.Cube):
                2D - standard deviation of model orography height on pp grid.
                In m.
            pporo_cube (iris.cube.Cube):
                2D - pp orography. In m
            modoro_cube (iris.cube.Cube):
                2D - model orography interpolated on pp grid. In m
            modres (float):
                original average model resolution in m
            height_levels_cube (iris.cube.Cube):
                3D or 1D - height of input velocity field.
                Can be position dependent
            z0_cube (iris.cube.Cube):
                2D - vegetative roughness length in m. If not given, do not do
                any RC
        """
        for cube in [
                a_over_s_cube, sigma_cube, pporo_cube, modoro_cube, z0_cube,
                height_levels_cube
        ]:
            if cube is not None:
                check_cube_not_float64(cube, fix=True)

        # Standard Python 'float' type is either single or double depending on
        # system and there is no reliable method of finding which from the
        # variable. So force to numpy.float32 by default.
        modres = np.float32(modres)

        x_name, y_name, _, _ = self.find_coord_names(pporo_cube)
        # Some checking that all the grids match
        if not (self.check_ancils(a_over_s_cube, sigma_cube, z0_cube,
                                  pporo_cube, modoro_cube)):
            raise ValueError("ancillary grids are not consistent")
        # I want this ordering. Careful: iris.cube.Cube.slices is unreliable.
        self.a_over_s = next(a_over_s_cube.slices([y_name, x_name]))
        self.sigma = next(sigma_cube.slices([y_name, x_name]))
        try:
            self.z_0 = next(z0_cube.slices([y_name, x_name]))
        except AttributeError:
            self.z_0 = z0_cube
        self.pp_oro = next(pporo_cube.slices([y_name, x_name]))
        self.model_oro = next(modoro_cube.slices([y_name, x_name]))
        self.ppres = self.calc_av_ppgrid_res(pporo_cube)
        self.modres = modres
        self.height_levels = height_levels_cube
        self.x_name = None
        self.y_name = None
        self.z_name = None
        self.t_name = None
Exemplo n.º 18
0
 def test_float64_cube_time_coord_points_ok(self):
     """Test a pass of a cube with 64 bit time coord points."""
     self.cube.coord("time").points = (
         self.cube.coord("time").points.astype(np.float64))
     check_cube_not_float64(self.cube)
Exemplo n.º 19
0
def process(output_data, target_grid=None, source_landsea=None,
            metadata_dict=None, regrid_mode='bilinear',
            extrapolation_mode='nanmask', landmask_vicinity=25000,
            fix_float64=False):
    """Standardises a cube by one or more of regridding, updating meta-data etc

    Standardise a source cube. Available options are regridding
    (bi-linear or nearest-neighbour, optionally with land-mask
    awareness), updating meta-data and converting float64 data to
    float32. A check for float64 data compliance can be made by only
    specifying a source cube with no other arguments.

    Args:
        output_data (iris.cube.Cube):
            Output cube. If the only argument, then it is checked bor float64
            data.
        target_grid (iris.cube.Cube):
            If specified, then regridding of the source against the target
            grid is enabled. If also using landmask-aware regridding then this
            must be land_binary_mask data.
            Default is None.
        source_landsea (iris.cube.Cube):
            A cube describing the land_binary_mask on the source-grid if
            coastline-aware regridding is required.
            Default is None.
        metadata_dict (dict):
            Dictionary containing required changes that will be applied to
            the metadata.
            Default is None.
        regrid_mode (str):
            Selects which regridding techniques to use. Default uses
            iris.analysis.Linear(); "nearest" uses Nearest() (Use for less
            continuous fields, e.g precipitation.); "nearest-with-mask"
            ensures that target data are sources from points with the same
            mask value (Use for coast-line-dependant variables
            like temperature).
        extrapolation_mode (str):
            Mode to use for extrapolating data into regions beyond the limits
            of the source_data domain. Refer to online documentation for
            iris.analysis.
            Modes are -
            extrapolate -The extrapolation points will take their values
            from the nearest source point.
            nan - The extrapolation points will be set to NaN.
            error - A ValueError exception will be raised notifying an attempt
            to extrapolate.
            mask - The extrapolation points will always be masked, even if
            the source data is not a MaskedArray.
            nanmask - If the source data is a MaskedArray the extrapolation
            points will be masked. Otherwise they will be set to NaN.
            Defaults is 'nanmask'.
        landmask_vicinity (float):
            Radius of vicinity to search for a coastline, in metres.
            Defaults is 25000 m
        fix_float64 (bool):
            If True, checks and fixes cube for float64 data. Without this
            option an exception will be raised if float64 data is found but no
            fix applied.
            Default is False.

    Returns:
        output_data (iris.cube.Cube):
            Processed cube.

    Raises:
        ValueError:
            If source landsea is supplied but regrid mode not
            nearest-with-mask.
        ValueError:
            If source landsea is supplied but not target grid.
        ValueError:
            If regrid_mode is "nearest-with-mask" but no landmask cube has
            been provided.

    Warns:
        warning:
            If the 'source_landsea' did not have a cube named land_binary_mask.
        warning:
            If the 'target_grid' did not have a cube named land_binary_mask.

    """
    if (source_landsea and
            "nearest-with-mask" not in regrid_mode):
        msg = ("Land-mask file supplied without appropriate regrid_mode. "
               "Use --regrid_mode=nearest-with-mask.")
        raise ValueError(msg)

    if source_landsea and not target_grid:
        msg = ("Cannot specify input_landmask_filepath without "
               "target_grid_filepath")
        raise ValueError(msg)
    # Process
    # Re-grid with options:
    check_cube_not_float64(output_data, fix=fix_float64)
    # if a target grid file has been specified, then regrid optionally
    # applying float64 data check, metadata change, Iris nearest and
    # extrapolation mode as required.
    if target_grid:
        regridder = iris.analysis.Linear(
            extrapolation_mode=extrapolation_mode)

        if regrid_mode in ["nearest", "nearest-with-mask"]:
            regridder = iris.analysis.Nearest(
                extrapolation_mode=extrapolation_mode)

        output_data = output_data.regrid(target_grid, regridder)

        if regrid_mode in ["nearest-with-mask"]:
            if not source_landsea:
                msg = ("An argument has been specified that requires an input "
                       "landmask cube but none has been provided")
                raise ValueError(msg)

            if "land_binary_mask" not in source_landsea.name():
                msg = ("Expected land_binary_mask in input_landmask cube "
                       "but found {}".format(repr(source_landsea)))
                warnings.warn(msg)

            if "land_binary_mask" not in target_grid.name():
                msg = ("Expected land_binary_mask in target_grid cube "
                       "but found {}".format(repr(target_grid)))
                warnings.warn(msg)

            output_data = RegridLandSea(
                vicinity_radius=landmask_vicinity).process(
                output_data, source_landsea, target_grid)

        target_grid_attributes = (
            {k: v for (k, v) in target_grid.attributes.items()
             if 'mosg__' in k or 'institution' in k})
        amend_metadata(output_data, attributes=target_grid_attributes)
    # Change metadata only option:
    # if output file path and json metadata file specified,
    # change the metadata
    if metadata_dict:
        output_data = amend_metadata(output_data, **metadata_dict)

    check_cube_not_float64(output_data, fix=fix_float64)

    return output_data
Exemplo n.º 20
0
def main(argv=None):
    """
    Standardise a source cube. Available options are regridding (bilinear or
    nearest-neighbour, optionally with land-mask awareness), updating meta-data
    and converting float64 data to float32. A check for float64 data compliance
    can be made by only specify a source NetCDF file with no other arguments.
    """
    parser = ArgParser(
        description='Standardise a source data cube. Three main options are '
        'available; fixing float64 data, regridding and updating '
        'metadata. If regridding then additional options are '
        'available to use bilinear or nearest-neighbour '
        '(optionally with land-mask awareness) modes. If only a '
        'source file is specified with no other arguments, then '
        'an exception will be raised if float64 data are found on '
        'the source.')

    parser.add_argument('source_data_filepath',
                        metavar='SOURCE_DATA',
                        help='A cube of data that is to be standardised and '
                        'optionally fixed for float64 data, regridded '
                        'and meta data changed')

    parser.add_argument("--output_filepath",
                        metavar="OUTPUT_FILE",
                        default=None,
                        help="The output path for the processed NetCDF. "
                        "If only a source file is specified and no "
                        "output file, then the source will be checked"
                        "for float64 data.")

    regrid_group = parser.add_argument_group("Regridding options")
    regrid_group.add_argument(
        "--target_grid_filepath",
        metavar="TARGET_GRID",
        help=('If specified then regridding of the source '
              'against the target grid is enabled. If also using '
              'landmask-aware regridding, then this must be land_binary_mask '
              'data.'))

    regrid_group.add_argument(
        "--regrid_mode",
        default='bilinear',
        choices=['bilinear', 'nearest', 'nearest-with-mask'],
        help=('Selects which regridding technique to use. Default uses '
              'iris.analysis.Linear(); "nearest" uses Nearest() (Use for less '
              'continuous fields, e.g. precipitation.); "nearest-with-mask" '
              'ensures that target data are sourced from points with the same '
              'mask value (Use for coast-line-dependent variables like '
              'temperature).'))

    regrid_group.add_argument(
        "--extrapolation_mode",
        default='nanmask',
        help='Mode to use for extrapolating data into regions '
        'beyond the limits of the source_data domain. '
        'Refer to online documentation for iris.analysis. '
        'Modes are: '
        'extrapolate - The extrapolation points will '
        'take their value from the nearest source point. '
        'nan - The extrapolation points will be be '
        'set to NaN. '
        'error - A ValueError exception will be raised, '
        'notifying an attempt to extrapolate. '
        'mask  - The extrapolation points will always be '
        'masked, even if the source data is not a '
        'MaskedArray. '
        'nanmask - If the source data is a MaskedArray '
        'the extrapolation points will be masked. '
        'Otherwise they will be set to NaN. '
        'Defaults to nanmask.')

    regrid_group.add_argument(
        "--input_landmask_filepath",
        metavar="INPUT_LANDMASK_FILE",
        help=("A path to a NetCDF file describing the land_binary_mask on "
              "the source-grid if coastline-aware regridding is required."))

    regrid_group.add_argument(
        "--landmask_vicinity",
        metavar="LANDMASK_VICINITY",
        default=25000.,
        type=float,
        help=("Radius of vicinity to search for a coastline, in metres. "
              "Default value; 25000 m"))

    parser.add_argument("--fix_float64",
                        action='store_true',
                        default=False,
                        help="Check and fix cube for float64 data. Without "
                        "this option an exception will be raised if "
                        "float64 data is found but no fix applied.")

    parser.add_argument("--json_file",
                        metavar="JSON_FILE",
                        default=None,
                        help='Filename for the json file containing required '
                        'changes that will be applied '
                        'to the metadata. Defaults to None.')

    args = parser.parse_args(args=argv)

    if args.target_grid_filepath or args.json_file or args.fix_float64:
        if not args.output_filepath:
            msg = ("An argument has been specified that requires an output "
                   "filepath but none has been provided")
            raise ValueError(msg)

    if (args.input_landmask_filepath
            and "nearest-with-mask" not in args.regrid_mode):
        msg = ("Land-mask file supplied without appropriate regrid_mode. "
               "Use --regrid_mode=nearest-with-mask.")
        raise ValueError(msg)

    if args.input_landmask_filepath and not args.target_grid_filepath:
        msg = ("Cannot specify input_landmask_filepath without "
               "target_grid_filepath")
        raise ValueError(msg)

    # source file data path is a mandatory argument
    output_data = load_cube(args.source_data_filepath)

    if args.fix_float64:
        check_cube_not_float64(output_data, fix=True)
    else:
        check_cube_not_float64(output_data, fix=False)

    # Re-grid with options:
    # if a target grid file has been specified, then regrid optionally
    # applying float64 data check, metadata change, Iris nearest and
    # extrapolation mode as required.

    if args.target_grid_filepath:

        target_grid = load_cube(args.target_grid_filepath)

        regridder = iris.analysis.Linear(
            extrapolation_mode=args.extrapolation_mode)

        if args.regrid_mode in ["nearest", "nearest-with-mask"]:
            regridder = iris.analysis.Nearest(
                extrapolation_mode=args.extrapolation_mode)

        output_data = output_data.regrid(target_grid, regridder)

        if args.regrid_mode in ["nearest-with-mask"]:
            if not args.input_landmask_filepath:
                msg = ("An argument has been specified that requires an input "
                       "landmask filepath but none has been provided")
                raise ValueError(msg)

            source_landsea = load_cube(args.input_landmask_filepath)
            if "land_binary_mask" not in source_landsea.name():
                msg = ("Expected land_binary_mask in input_landmask_filepath "
                       "but found {}".format(repr(source_landsea)))
                warnings.warn(msg)
            if "land_binary_mask" not in target_grid.name():
                msg = ("Expected land_binary_mask in target_grid_filepath "
                       "but found {}".format(repr(target_grid)))
                warnings.warn(msg)
            output_data = RegridLandSea(
                vicinity_radius=args.landmask_vicinity).process(
                    output_data, source_landsea, target_grid)

        target_grid_attributes = ({
            k: v
            for (k, v) in target_grid.attributes.items()
            if 'mosg__' in k or 'institution' in k
        })
        amend_metadata(output_data, attributes=target_grid_attributes)

    # Change metadata only option:
    # if output file path and json metadata file specified,
    # change the metadata
    if args.json_file:
        with open(args.json_file, 'r') as input_file:
            metadata_dict = json.load(input_file)
        output_data = amend_metadata(output_data, **metadata_dict)

    # Check and fix for float64 data only option:
    if args.fix_float64:
        check_cube_not_float64(output_data, fix=True)

    if args.output_filepath:
        save_netcdf(output_data, args.output_filepath)