Beispiel #1
0
def process(
    orography: cli.inputcube,
    land_sea_mask: cli.inputcube = None,
    *,
    bands_config: cli.inputjson = None,
):
    """Runs topographic bands mask generation.

    Reads orography and land_sea_mask fields of a cube. Creates a series of
    masks, where each mask excludes data below or equal to the lower threshold
    and excludes data above the upper threshold.

    Args:
        orography (iris.cube.Cube):
            The orography on a standard grid.
        land_sea_mask (iris.cube.Cube):
            The land mask on standard grid, with land points set to one and
            sea points set to zero. If provided sea points will be set
            to zero in every band. If no land mask is provided, sea points will
            be included in the appropriate topographic band.
        bands_config (dict):
            Definition of orography bands required.
            The expected format of the dictionary is e.g
            {'bounds':[[0, 50], [50, 200]], 'units': 'm'}
            The default dictionary has the following form:
            {'bounds': [[-500., 50.], [50., 100.],
            [100., 150.],[150., 200.], [200., 250.],
            [250., 300.], [300., 400.], [400., 500.],
            [500., 650.],[650., 800.], [800., 950.],
            [950., 6000.]], 'units': 'm'}

    Returns:
        iris.cube.Cube:
            list of orographic band mask cube.

    """
    from improver.generate_ancillaries.generate_ancillary import (
        GenerateOrographyBandAncils,
        THRESHOLDS_DICT,
    )

    if bands_config is None:
        bands_config = THRESHOLDS_DICT

    if land_sea_mask:
        land_sea_mask = next(
            land_sea_mask.slices(
                [land_sea_mask.coord(axis="y"),
                 land_sea_mask.coord(axis="x")]))

    orography = next(
        orography.slices(
            [orography.coord(axis="y"),
             orography.coord(axis="x")]))

    result = GenerateOrographyBandAncils()(orography,
                                           bands_config,
                                           landmask=land_sea_mask)
    result = result.concatenate_cube()
    return result
 def test_nonzero_landband_cube(self):
     """test that a correct cube is produced when neither landband
     bound is zero."""
     result = GenOrogMasks().gen_orography_masks(
         self.orography, self.landmask, self.nonzero_land_threshold)
     self.assertEqual(
         result.coord('topographic_zone').points,
         np.mean(self.nonzero_land_threshold))
Beispiel #3
0
 def test_high_landband_cube(self):
     """test that a correct cube is produced when the land band is
     higher than any land in the test cube."""
     result = GenOrogMasks().gen_orography_masks(self.orography,
                                                 self.landmask,
                                                 self.high_land_threshold)
     self.assertEqual(
         result.coord("topographic_zone").points,
         np.mean(self.high_land_threshold))
 def test_unit_conversion_for_landband_data(self):
     """test correct mask is produced for land bands > 0m"""
     land_threshold = [0, 0.05]
     threshold_units = "km"
     result = GenOrogMasks().gen_orography_masks(
         self.orography, self.landmask, land_threshold, units=threshold_units
     )
     self.assertArrayAlmostEqual(result.data, self.exp_landmask)
     self.assertEqual(result.coord("topographic_zone").units, Unit("m"))
 def test_valleyband_cube(self):
     """test correct cube data is produced for land bands < 0m"""
     result = GenOrogMasks().gen_orography_masks(self.orography,
                                                 self.landmask,
                                                 self.valley_key,
                                                 self.valley_threshold)[0]
     self.assertEqual(result.attributes['Topographical Type'], 'Land')
     self.assertEqual(
         result.coord('topographic_bound_lower').points,
         self.valley_threshold[0])
     self.assertEqual(
         result.coord('topographic_bound_upper').points,
         self.valley_threshold[1])
 def test_maxband_cube(self):
     """test correct cube data is produced for land bands > max"""
     result = GenOrogMasks().gen_orography_masks(self.orography,
                                                 self.landmask,
                                                 self.max_key,
                                                 self.max_threshold)[0]
     self.assertEqual(result.attributes['Topographical Type'],
                      'Max_Land_Threshold')
     self.assertEqual(
         result.coord('topographic_bound_lower').points,
         self.max_threshold[0])
     msg = 'Expected to find exactly 1  coordinate, but found none.'
     with self.assertRaisesRegexp(CoordinateNotFoundError, msg):
         result.coord('topographic_bound_upper')
def process(orography, landmask=None, thresholds_dict=None):
    """Runs topographic bands mask generation.

    Reads orography and landmask fields of a cube. Creates a series of masks,
    where each mask excludes data below or equal to the lower threshold and
    excludes data above the upper threshold.

    Args:
        orography (iris.cube.Cube):
            The orography a standard grid.
        landmask (iris.cube.Cube):
            The land mask on standard grid. If provided data points are set to
            zero in every band.
            Default is None.
        thresholds_dict (dict):
            Definition of orography bands required.
            The expected format of the dictionary is e.g
            {'bounds':[[0, 50], [50, 200]], 'units': 'm'}
            The default dictionary has the following form:
            {'bounds': [[-500., 50.], [50., 100.],
            [100., 150.],[150., 200.], [200., 250.],
            [250., 300.], [300., 400.], [400., 500.],
            [500., 650.],[650., 800.], [800., 950.],
            [950., 6000.]], 'units': 'm'}

    Returns:
        iris.cube.Cube:
            list of orographic band mask cube.

    """
    if landmask:
        landmask = next(
            landmask.slices(
                [landmask.coord(axis='y'),
                 landmask.coord(axis='x')]))

    orography = next(
        orography.slices(
            [orography.coord(axis='y'),
             orography.coord(axis='x')]))

    if thresholds_dict is None:
        thresholds_dict = THRESHOLDS_DICT

    result = GenerateOrographyBandAncils().process(orography,
                                                   thresholds_dict,
                                                   landmask=landmask)
    result = result.concatenate_cube()
    return result
Beispiel #8
0
 def test_landband_cube(self):
     """test correct cube data is produced for land bands > 0m"""
     result = GenOrogMasks().gen_orography_masks(self.orography,
                                                 self.landmask,
                                                 self.land_threshold)
     self.assertEqual(
         result.attributes["topographic_zones_include_seapoints"], "False")
     self.assertEqual(
         result.coord("topographic_zone").points,
         np.mean(self.land_threshold))
     self.assertEqual(
         result.coord("topographic_zone").bounds[0][0],
         self.land_threshold[0])
     self.assertEqual(
         result.coord("topographic_zone").bounds[0][1],
         self.land_threshold[1])
 def test_landband_cube(self):
     """test correct cube data is produced for land bands > 0m"""
     result = GenOrogMasks().gen_orography_masks(self.orography,
                                                 self.landmask,
                                                 self.land_key,
                                                 self.land_threshold)
     self.assertEqual(result.attributes['Topographical Type'], 'Land')
     self.assertEqual(
         result.coord('topographic_zone').points,
         np.mean(self.land_threshold))
     self.assertEqual(
         result.coord('topographic_zone').bounds[0][0],
         self.land_threshold[0])
     self.assertEqual(
         result.coord('topographic_zone').bounds[0][1],
         self.land_threshold[1])
    def process(self, orography, thresholds_dict, landmask=None):
        """Calculate the weights depending upon where the orography point is
        within the topographic zones.

        Args:
            orography (iris.cube.Cube):
                Orography on standard grid.
            thresholds_dict (dict):
                Definition of orography bands required.
                The expected format of the dictionary is e.g.
                `{'bounds': [[0, 50], [50, 200]], 'units': 'm'}`
            landmask (iris.cube.Cube):
                Land mask on standard grid, with land points set to one and
                sea points set to zero. If provided sea points are masked
                out in the output array.
        Returns:
            iris.cube.Cube:
                Cube containing the weights depending upon where the orography
                point is within the topographic zones.
        """
        # Check that orography is a 2d cube.
        if len(orography.shape) != 2:
            msg = ("The input orography cube should be two-dimensional."
                   "The input orography cube has {} dimensions".format(
                       len(orography.shape)))
            raise InvalidCubeError(msg)

        # Find bands and midpoints from bounds.
        bands = np.array(thresholds_dict["bounds"], dtype=np.float32)
        threshold_units = thresholds_dict["units"]

        # Create topographic_zone_cube first, so that a cube is created for
        # each band. This will allow the data for neighbouring bands to be
        # put into the cube.
        mask_data = np.zeros(orography.shape, dtype=np.float32)
        topographic_zone_cubes = iris.cube.CubeList([])
        for band in bands:
            sea_points_included = not landmask
            topographic_zone_cube = _make_mask_cube(
                mask_data,
                orography.coords(),
                band,
                threshold_units,
                sea_points_included=sea_points_included,
            )
            topographic_zone_cubes.append(topographic_zone_cube)
        topographic_zone_weights = topographic_zone_cubes.concatenate_cube()
        topographic_zone_weights.data = topographic_zone_weights.data.astype(
            np.float32)

        # Ensure topographic_zone coordinate units is equal to orography units.
        topographic_zone_weights.coord("topographic_zone").convert_units(
            orography.units)

        # Read bands from cube, now that they can be guaranteed to be in the
        # same units as the orography. The bands are converted to a list, so
        # that they can be iterated through.
        bands = list(topographic_zone_weights.coord("topographic_zone").bounds)
        midpoints = topographic_zone_weights.coord("topographic_zone").points

        # Raise a warning, if orography extremes are outside the extremes of
        # the bands.
        if np.max(orography.data) > np.max(bands):
            msg = ("The maximum orography is greater than the uppermost band. "
                   "This will potentially cause the topographic zone weights "
                   "to not sum to 1 for a given grid point.")
            warnings.warn(msg)

        if np.min(orography.data) < np.min(bands):
            msg = ("The minimum orography is lower than the lowest band. "
                   "This will potentially cause the topographic zone weights "
                   "to not sum to 1 for a given grid point.")
            warnings.warn(msg)

        # Insert the appropriate weights into the topographic zone cube. This
        # includes the weights from the band that a point is in, as well as
        # the contribution from an adjacent band.
        for band_number, band in enumerate(bands):
            # Determine the points that are within the specified band.
            mask_y, mask_x = np.where((orography.data > band[0])
                                      & (orography.data <= band[1]))
            orography_band = np.full(orography.shape, np.nan, dtype=np.float32)
            orography_band[mask_y, mask_x] = orography.data[mask_y, mask_x]

            # Calculate the weights. This involves calculating the
            # weights for all the orography but only inserting weights
            # that are within the band into the topographic_zone_weights cube.
            weights = self.calculate_weights(orography_band, band)
            topographic_zone_weights.data[band_number, mask_y,
                                          mask_x] = weights[mask_y, mask_x]

            # Calculate the contribution to the weights from the adjacent
            # lower band.
            topographic_zone_weights.data = self.add_weight_to_lower_adjacent_band(
                topographic_zone_weights.data,
                orography_band,
                midpoints[band_number],
                band_number,
            )

            # Calculate the contribution to the weights from the adjacent
            # upper band.
            topographic_zone_weights.data = self.add_weight_to_upper_adjacent_band(
                topographic_zone_weights.data,
                orography_band,
                midpoints[band_number],
                band_number,
                len(bands) - 1,
            )

        # Metadata updates
        topographic_zone_weights.rename("topographic_zone_weights")
        topographic_zone_weights.units = Unit("1")

        # Mask output weights using a land-sea mask.
        topographic_zone_masked_weights = iris.cube.CubeList([])
        for topographic_zone_slice in topographic_zone_weights.slices_over(
                "topographic_zone"):
            if landmask:
                topographic_zone_slice.data = GenerateOrographyBandAncils(
                ).sea_mask(landmask.data, topographic_zone_slice.data)
            topographic_zone_masked_weights.append(topographic_zone_slice)
        topographic_zone_weights = topographic_zone_masked_weights.merge_cube()
        return topographic_zone_weights