Exemple #1
0
 def test_exception_for_degrees_input(self):
     """Test that an exception is raised if the input cube has spatial
     coordinates with units that cannot be converted to the default unit of
     the projection."""
     self.cube.coord(axis="x").units = "degrees"
     msg = "Cube passed to transform_grid_to_lat_lon does not have an x coordinate"
     with self.assertRaisesRegex(ValueError, msg):
         transform_grid_to_lat_lon(self.cube)
    def calc_lats_lons(cube):
        """
        Calculate the lats and lons of each point from a non-latlon cube,
        or output a 2d array of lats and lons, if the input cube has latitude
        and longitude coordinates.

        Args:
            cube (iris.cube.Cube):
                cube containing x and y axis
        Returns:
            (tuple) : tuple containing:
                **lats** (np.array):
                    2d Array of latitudes for each point.
                **lons** (np.array):
                    2d Array of longitudes for each point.

        """
        trg_crs = lat_lon_determine(cube)
        if trg_crs is not None:
            xycube = next(
                cube.slices([cube.coord(axis='y'),
                             cube.coord(axis='x')]))
            lats, lons = transform_grid_to_lat_lon(xycube)
        else:
            lats_row = cube.coord('latitude').points
            lons_col = cube.coord('longitude').points
            lats = np.repeat(lats_row[:, np.newaxis], len(lons_col), axis=1)
            lons = np.repeat(lons_col[np.newaxis, :], len(lats_row), axis=0)
        return lats, lons
    def calc_lats_lons(cube: Cube) -> Tuple[ndarray, ndarray]:
        """
        Calculate the lats and lons of each point from a non-latlon cube,
        or output a 2d array of lats and lons, if the input cube has latitude
        and longitude coordinates.

        Args:
            cube:
                cube containing x and y axis

        Returns:
            - 2d Array of latitudes for each point.
            - 2d Array of longitudes for each point.
        """
        trg_crs = lat_lon_determine(cube)
        if trg_crs is not None:
            xycube = next(
                cube.slices([cube.coord(axis="y"),
                             cube.coord(axis="x")]))
            lats, lons = transform_grid_to_lat_lon(xycube)
        else:
            lats_row = cube.coord("latitude").points
            lons_col = cube.coord("longitude").points
            lats = np.repeat(lats_row[:, np.newaxis], len(lons_col), axis=1)
            lons = np.repeat(lons_col[np.newaxis, :], len(lats_row), axis=0)
        return lats, lons
Exemple #4
0
    def _get_coordinate_pairs(cube):
        """
        Create an array containing all the pairs of coordinates that describe
        y-x points in the grid.

        Args:
            cube (iris.cube.Cube):
                The cube from which the y-x grid is being taken.
        Returns:
            numpy.array:
                A numpy array containing all the pairs of coordinates that describe
                the y-x points in the grid. This array is 2-dimensional, with
                shape (2,  (len(y-points) * len(x-points))).
        """
        if lat_lon_determine(cube) is not None:
            yy, xx = transform_grid_to_lat_lon(cube)
        else:
            latitudes = cube.coord("latitude").points
            longitudes = cube.coord("longitude").points.copy()

            # timezone finder works using -180 to 180 longitudes.
            if (longitudes > 180).any():
                longitudes[longitudes > 180] -= 180
                if ((longitudes > 180) | (longitudes < -180)).any():
                    msg = (
                        "TimezoneFinder requires longitudes between -180 "
                        "and 180 degrees. Longitude found outside that range."
                    )
                    raise ValueError(msg)
            yy, xx = np.meshgrid(latitudes, longitudes, indexing="ij")

        return np.stack([yy.flatten(), xx.flatten()], axis=1)
Exemple #5
0
 def test_transform_grid(self):
     """Test transformation of grid for equal area grid with spatial
     coordinates defined in metres."""
     result_lats, result_lons = transform_grid_to_lat_lon(self.cube)
     self.assertIsInstance(result_lats, np.ndarray)
     self.assertIsInstance(result_lons, np.ndarray)
     assert_almost_equal(result_lons, self.expected_lons)
     assert_almost_equal(result_lats, self.expected_lats)
Exemple #6
0
    def test_non_metre_input(self):
        """Test transformation of grid for equal area grid with spatial
        coordinates defined in kilometres."""

        self.cube.coord(axis="x").convert_units("km")
        self.cube.coord(axis="y").convert_units("km")

        result_lats, result_lons = transform_grid_to_lat_lon(self.cube)
        self.assertIsInstance(result_lats, np.ndarray)
        self.assertIsInstance(result_lons, np.ndarray)
        assert_almost_equal(result_lons, self.expected_lons)
        assert_almost_equal(result_lats, self.expected_lats)
Exemple #7
0
    def process(self, cube):
        """
        Calculate the daynight mask for the provided cube. Note that only the
        hours and minutes of the dtval variable are used. To ensure consistent
        behaviour with changes of second or subsecond precision, the second
        component is added to the time object. This means that when the hours
        and minutes are used, we have correctly rounded to the nearest minute,
        e.g.::

           dt(2017, 1, 1, 11, 59, 59) -- +59 --> dt(2017, 1, 1, 12, 0, 58)
           dt(2017, 1, 1, 12, 0, 1)   -- +1  --> dt(2017, 1, 1, 12, 0, 2)
           dt(2017, 1, 1, 12, 0, 30)  -- +30 --> dt(2017, 1, 1, 12, 1, 0)

        Args:
            cube (iris.cube.Cube):
                input cube

        Returns:
            iris.cube.Cube:
                daynight mask cube, daytime set to self.day
                nighttime set to self.night.
                The resulting cube will be the same shape as
                the time, y, and x coordinate, other coordinates
                will be ignored although they might appear as attributes
                on the cube as it is extracted from the first slice.
        """
        daynight_mask = self._create_daynight_mask(cube)

        modified_masks = iris.cube.CubeList()
        for mask_cube in daynight_mask.slices_over("time"):
            dtval = mask_cube.coord("time").cell(0).point
            day_of_year = (dtval - dt.datetime(dtval.year, 1, 1)).days
            dtval = dtval + dt.timedelta(seconds=dtval.second)
            utc_hour = (dtval.hour * 60.0 + dtval.minute) / 60.0
            trg_crs = lat_lon_determine(mask_cube)
            # Grids that are not Lat Lon
            if trg_crs is not None:
                lats, lons = transform_grid_to_lat_lon(mask_cube)
                solar_el = calc_solar_elevation(lats, lons, day_of_year, utc_hour)
                mask_cube.data[np.where(solar_el > 0.0)] = self.day
            else:
                mask_cube = self._daynight_lat_lon_cube(
                    mask_cube, day_of_year, utc_hour
                )
            modified_masks.append(mask_cube)
        return modified_masks.merge_cube()
Exemple #8
0
    def process(self,
                target_grid: Cube,
                time: datetime,
                new_title: str = None) -> Cube:
        """Calculate the local solar time over the specified grid.

        Args:
            target_grid:
                A cube containing the desired spatial grid.
            time:
                The valid time at which to evaluate the local solar time.
            new_title:
                New title for the output cube attributes. If None, this attribute is
                left out since it has no prescribed standard.

        Returns:
            A cube containing local solar time, on the same spatial grid as target_grid.
        """

        if lat_lon_determine(target_grid) is not None:
            _, lons = transform_grid_to_lat_lon(target_grid)
        else:
            _, lons = get_grid_y_x_values(target_grid)

        day_of_year = get_day_of_year(time)
        utc_hour = get_hour_of_day(time)

        solar_time_data = calc_solar_time(lons,
                                          day_of_year,
                                          utc_hour,
                                          normalise=True)

        solar_time_cube = self._create_solar_time_cube(solar_time_data,
                                                       target_grid, time,
                                                       new_title)

        return solar_time_cube
    def process(self, cube):
        """
        Calculate the daynight mask for the provided cube

        Args:
            cube (iris.cube.Cube):
                input cube

        Returns:
            daynight_mask (iris.cube.Cube):
                daynight mask cube, daytime set to self.day
                nighttime set to self.night.
                The resulting cube will be the same shape as
                the time, y, and x coordinate, other coordinates
                will be ignored although they might appear as attributes
                on the cube as it is extracted from the first slice.
        """
        daynight_mask = self._create_daynight_mask(cube)
        dtvalues = iris_time_to_datetime(daynight_mask.coord('time'))
        for i, dtval in enumerate(dtvalues):
            mask_cube = daynight_mask[i]
            day_of_year = (dtval - dt.datetime(dtval.year, 1, 1)).days
            utc_hour = (dtval.hour * 60.0 + dtval.minute) / 60.0
            trg_crs = lat_lon_determine(mask_cube)
            # Grids that are not Lat Lon
            if trg_crs is not None:
                lats, lons = transform_grid_to_lat_lon(mask_cube)
                solar_el = calc_solar_elevation(lats, lons, day_of_year,
                                                utc_hour)
                mask_cube.data[np.where(solar_el > 0.0)] = self.day
            else:
                mask_cube = self._daynight_lat_lon_cube(
                    mask_cube, day_of_year, utc_hour)
            daynight_mask.data[i, ::] = mask_cube.data

        return daynight_mask
Exemple #10
0
    def process(self, cube_in: Cube, cube_in_mask: Cube, cube_out_mask: Cube) -> Cube:
        """
        Regridding considering land_sea mask. please note cube_in must use
        lats/lons rectlinear system(GeogCS). cube_in_mask and cube_in could be
        different  resolution. cube_out could be either in lats/lons rectlinear
        system or LambertAzimuthalEqualArea system. Grid points in cube_out
        domain but not in cube_in domain will be masked.

        Args:
            cube_in:
                Cube of data to be regridded.
            cube_in_mask:
                Cube of land_binary_mask data ((land:1, sea:0). used to determine
                where the input model data is representing land and sea points.
            cube_out_mask:
                Cube of land_binary_mask data on target grid (land:1, sea:0).

        Returns:
            Regridded result cube.
        """
        # if cube_in's coordinate descending, make it assending.
        # if mask considered, reverse mask cube's coordinate if descending
        cube_in = ensure_ascending_coord(cube_in)
        if WITH_MASK in self.regrid_mode:
            cube_in_mask = ensure_ascending_coord(cube_in_mask)

        # check if input source grid is on even-spacing, ascending lat/lon system
        # return grid spacing for latitude and logitude
        lat_spacing, lon_spacing = calculate_input_grid_spacing(cube_in)

        # Gather output latitude/longitudes from output template cube
        if (
            cube_out_mask.coord(axis="x").standard_name == "projection_x_coordinate"
            and cube_out_mask.coord(axis="y").standard_name == "projection_y_coordinate"
        ):
            out_latlons = np.dstack(transform_grid_to_lat_lon(cube_out_mask)).reshape(
                (-1, 2)
            )
        else:
            out_latlons = latlon_from_cube(cube_out_mask)

        # Subset the input cube so that extra spatial area beyond the output is removed
        # This is a performance optimisation to reduce the size of the dataset being processed
        total_out_point_num = out_latlons.shape[0]
        lat_max, lon_max = out_latlons.max(axis=0)
        lat_min, lon_min = out_latlons.min(axis=0)
        if WITH_MASK in self.regrid_mode:
            cube_in, cube_in_mask = slice_mask_cube_by_domain(
                cube_in, cube_in_mask, (lat_max, lon_max, lat_min, lon_min)
            )
        else:  # not WITH_MASK
            cube_in = slice_cube_by_domain(
                cube_in, (lat_max, lon_max, lat_min, lon_min)
            )

        # group cube_out's grid points into outside or inside cube_in's domain
        (
            outside_input_domain_index,
            inside_input_domain_index,
        ) = group_target_points_with_source_domain(cube_in, out_latlons)

        # exclude out-of-input-domain target point here
        if len(outside_input_domain_index) > 0:
            out_latlons = out_latlons[inside_input_domain_index]

        # Gather input latitude/longitudes from input cube
        in_latlons = latlon_from_cube(cube_in)
        # Number of grid points in X dimension is used to work out length of flattened array
        # stripes for finding surrounding points for bilinear interpolation
        in_lons_size = cube_in.coord(axis="x").shape[0]  # longitude

        # Reshape input data so that spatial dimensions can be handled as one
        in_values, lats_index, lons_index = flatten_spatial_dimensions(cube_in)

        # Locate nearby input points for output points
        indexes = basic_indexes(
            out_latlons, in_latlons, in_lons_size, lat_spacing, lon_spacing
        )

        if WITH_MASK in self.regrid_mode:
            in_classified = classify_input_surface_type(cube_in_mask, in_latlons)

            out_classified = classify_output_surface_type(cube_out_mask)

            if len(outside_input_domain_index) > 0:
                out_classified = out_classified[inside_input_domain_index]

            # Identify mismatched surface types from input and output classifications
            surface_type_mask = similar_surface_classify(
                in_classified, out_classified, indexes
            )

        # Initialise distances and weights to zero. Weights are only used for the bilinear case
        distances = np.zeros((out_latlons.shape[0], NUM_NEIGHBOURS), dtype=np.float32)
        weights = np.zeros((out_latlons.shape[0], NUM_NEIGHBOURS), dtype=np.float32)

        # handle nearest option
        if NEAREST in self.regrid_mode:
            for i in range(NUM_NEIGHBOURS):
                distances[:, i] = np.square(
                    in_latlons[indexes[:, i], 0] - out_latlons[:, 0]
                ) + np.square(in_latlons[indexes[:, i], 1] - out_latlons[:, 1])

            # for nearest-with-mask-2,adjust indexes and distance for mismatched
            # surface type location
            if WITH_MASK in self.regrid_mode:
                distances, indexes = nearest_with_mask_regrid(
                    distances,
                    indexes,
                    surface_type_mask,
                    in_latlons,
                    out_latlons,
                    in_classified,
                    out_classified,
                    self.vicinity,
                )

            # apply nearest distance rule
            output_flat = nearest_regrid(distances, indexes, in_values)

        elif BILINEAR in self.regrid_mode:
            # Assume all four nearby points are same surface type and calculate default weights
            # These will be updated for mask/mismatched surface type further below
            index_range = np.arange(weights.shape[0])
            weights[index_range] = basic_weights(
                index_range, indexes, out_latlons, in_latlons, lat_spacing, lon_spacing,
            )

            if WITH_MASK in self.regrid_mode:
                # For bilinear-with-mask-2, adjust weights and indexes for mismatched
                # surface type locations
                weights, indexes = adjust_for_surface_mismatch(
                    in_latlons,
                    out_latlons,
                    in_classified,
                    out_classified,
                    weights,
                    indexes,
                    surface_type_mask,
                    in_lons_size,
                    self.vicinity,
                    lat_spacing,
                    lon_spacing,
                )

            # apply bilinear rule
            output_flat = apply_weights(indexes, in_values, weights)

        # check if we need mask cube_out grid points which are out of cube_in range
        if len(outside_input_domain_index) > 0:
            output_flat = mask_target_points_outside_source_domain(
                total_out_point_num,
                outside_input_domain_index,
                inside_input_domain_index,
                output_flat,
            )
        # Un-flatten spatial dimensions and put into output cube
        output_array = unflatten_spatial_dimensions(
            output_flat, cube_out_mask, in_values, lats_index, lons_index
        )
        output_cube = create_regrid_cube(output_array, cube_in, cube_out_mask)

        return output_cube
Exemple #11
0
    def _calc_clearsky_solar_radiation_data(
        self,
        target_grid: Cube,
        irradiance_times: List[datetime],
        surface_altitude: ndarray,
        linke_turbidity: ndarray,
        temporal_spacing: int,
    ) -> ndarray:
        """Evaluate the gridded clearsky solar radiation data over the specified period,
        calculated on the same spatial grid points as target_grid.

        Args:
            target_grid:
                Cube containing the target spatial grid on which to evaluate irradiance.
            irradiance_times:
                Datetimes at which to evaluate the irradiance data.
            surface_altitude:
                Surface altitude data, specified in metres.
            linke_turbidity:
                Linke turbidity data.
            temporal_spacing:
                The time stepping, specified in mins, used in the integration of solar
                irradiance to produce the accumulated solar radiation.

        Returns:
            Gridded irradiance values evaluated over the specified times.
        """
        if lat_lon_determine(target_grid) is not None:
            lats, lons = transform_grid_to_lat_lon(target_grid)
        else:
            lats, lons = get_grid_y_x_values(target_grid)
        irradiance_data = np.zeros(
            shape=(
                len(irradiance_times),
                target_grid.coord(axis="Y").shape[0],
                target_grid.coord(axis="X").shape[0],
            ),
            dtype=np.float32,
        )

        for time_index, time_step in enumerate(irradiance_times):

            day_of_year = get_day_of_year(time_step)
            utc_hour = get_hour_of_day(time_step)

            zenith_angle = 90.0 - calc_solar_elevation(lats, lons, day_of_year,
                                                       utc_hour)

            irradiance_data[time_index, :, :] = self._calc_clearsky_ineichen(
                zenith_angle,
                day_of_year,
                surface_altitude=surface_altitude,
                linke_turbidity=linke_turbidity,
            )

        # integrate the irradiance data along the time dimension to get the
        # accumulated solar irradiance.
        solar_radiation_data = np.trapz(irradiance_data,
                                        dx=SECONDS_IN_MINUTE *
                                        temporal_spacing,
                                        axis=0)

        return solar_radiation_data