Esempio n. 1
0
 def test_upperbound_fails(self):
     """test checking that an exception is raised when the _make_mask_cube
     method is called with only an upper bound."""
     emsg = "should have both an upper and lower limit"
     with self.assertRaisesRegex(TypeError, emsg):
         _make_mask_cube(self.mask, self.coords, [None, self.upper],
                         self.units)
Esempio n. 2
0
 def test_wrong_number_of_bounds(self):
     """test checking that an exception is raised when the _make_mask_cube
     method is called with an incorrect number of bounds."""
     emsg = "should have only an upper and lower limit"
     with self.assertRaisesRegex(TypeError, emsg):
         _make_mask_cube(self.mask, self.coords, [0], self.units)
     with self.assertRaisesRegex(TypeError, emsg):
         _make_mask_cube(self.mask, self.coords, [0, 2, 4], self.units)
Esempio n. 3
0
 def test_upperbound(self):
     """test creating cube with upper threshold only set"""
     result = _make_mask_cube(self.mask,
                              self.key,
                              self.coords,
                              upper_threshold=self.upper)
     self.assertEqual(
         result.coords('topographic_bound_upper')[0].points, self.upper)
Esempio n. 4
0
 def test_nobounds(self):
     """test creating cube with neither upper nor lower threshold set"""
     result = _make_mask_cube(self.mask, self.key, self.coords)
     self.assertEqual(result.coord('longitude'), self.x_coord)
     self.assertEqual(result.coord('latitude'), self.y_coord)
     self.assertArrayEqual(result.data, self.mask)
     self.assertEqual(result.attributes['Topographical Type'],
                      self.key.title())
Esempio n. 5
0
 def test_cube_attribute_no_seapoints(self):
     """Test the new attribute is added to the cube."""
     result = _make_mask_cube(
         self.mask, self.coords, [self.lower, self.upper], self.units
     )
     self.assertEqual(
         result.attributes["topographic_zones_include_seapoints"], "False"
     )
Esempio n. 6
0
 def test_bothbounds(self):
     """test creating cube with both thresholds set"""
     result = _make_mask_cube(
         self.mask, self.coords, [self.lower, self.upper], self.units
     )
     self.assertEqual(result.coord("topographic_zone").bounds[0][1], self.upper)
     self.assertEqual(result.coord("topographic_zone").bounds[0][0], self.lower)
     self.assertEqual(
         result.coord("topographic_zone").points, np.mean([self.lower, self.upper])
     )
     self.assertEqual(result.coord("topographic_zone").units, Unit("m"))
Esempio n. 7
0
 def test_bothbounds(self):
     """test creating cube with both thresholds set"""
     result = _make_mask_cube(self.mask,
                              self.key,
                              self.coords,
                              topographic_bounds=[self.lower, self.upper])
     self.assertEqual(
         result.coord('topographic_zone').bounds[0][1], self.upper)
     self.assertEqual(
         result.coord('topographic_zone').bounds[0][0], self.lower)
     self.assertEqual(
         result.coord('topographic_zone').points,
         np.mean([self.lower, self.upper]))
    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