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
def calculate_input_grid_spacing(cube_in: Cube) -> Tuple[float, float]: """ Calculate grid spacing in latitude and logitude. Check if input source grid is on even-spacing and ascending lat/lon system. Args: cube_in: Input source cube. Returns: - Grid spacing in latitude, in degree. - Grid spacing in logitude, in degree. Raises: ValueError: If input grid is not on a latitude/longitude system or input grid coordinates are not ascending. """ # check if in lat/lon system if lat_lon_determine(cube_in) is not None: raise ValueError("Input grid is not on a latitude/longitude system") # calculate grid spacing lon_spacing = calculate_grid_spacing(cube_in, "degree", axis="x", rtol=1.0e-5) lat_spacing = calculate_grid_spacing(cube_in, "degree", axis="y", rtol=1.0e-5) if lon_spacing < 0 or lat_spacing < 0: raise ValueError("Input grid coordinates are not ascending.") return lat_spacing, lon_spacing
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 _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)
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()
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
def fast_nearest_neighbour(cube, sites, orography=None): """ Use iris coord.nearest_neighbour_index function to locate the nearest grid point to the given latitude/longitude pair. Performed on a 2D-surface; consider using the much slower iris.analysis.trajectory.interpolate method for a more correct nearest neighbour search with projection onto a spherical surface; this is typically much slower. Args: cube/sites : See process() above. orography (numpy.array): Array of orography data extracted from an iris.cube.Cube that corresponds to the grids on which all other input diagnostics will be provided (iris.cube.Cube.data). Returns: neighbours (numpy.array): See process() above. """ neighbours = np.empty(len(sites), dtype=[('i', 'i8'), ('j', 'i8'), ('dz', 'f8'), ('edgepoint', 'bool_')]) # Check cube coords are lat/lon, else transform lookup coordinates. trg_crs = lat_lon_determine(cube) imax = cube.coord(axis='y').shape[0] jmax = cube.coord(axis='x').shape[0] iname = cube.coord(axis='y').name() jname = cube.coord(axis='x').name() for i_site, site in enumerate(sites.values()): latitude, longitude, altitude = (site['latitude'], site['longitude'], site['altitude']) longitude, latitude = lat_lon_transform(trg_crs, latitude, longitude) i_latitude, j_longitude = get_nearest_coords( cube, latitude, longitude, iname, jname) dz_site_grid = 0. # Calculate SpotData site vertical displacement from model # orography. If site altitude set with np.nan or orography data # is unavailable, assume site is at equivalent altitude to nearest # neighbour. if orography is not None and altitude != np.nan: dz_site_grid = altitude - orography[i_latitude, j_longitude] else: dz_site_grid = 0. neighbours[i_site] = (int(i_latitude), int(j_longitude), dz_site_grid, (i_latitude == imax or j_longitude == jmax)) return neighbours
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