def test_variation(self): """Test that two cubes with slightly different coordinates return different hashes.""" hash_input1 = set_up_variable_cube(np.zeros((3, 3)).astype(np.float32)) hash_input2 = hash_input1.copy() latitude = hash_input2.coord('latitude') latitude_values = latitude.points * 1.001 latitude = latitude.copy(points=latitude_values) hash_input2.remove_coord("latitude") hash_input2.add_dim_coord(latitude, 0) result1 = create_coordinate_hash(hash_input1) result2 = create_coordinate_hash(hash_input2) self.assertNotEqual(result1, result2)
def setUp(self): """Set up cubes for use in testing.""" data = np.ones(9).reshape(3, 3).astype(np.float32) self.reference_cube = set_up_variable_cube(data, spatial_grid="equalarea") self.cube1 = self.reference_cube.copy() self.cube2 = self.reference_cube.copy() self.unmatched_cube = set_up_variable_cube(data, spatial_grid="latlon") self.diagnostic_cube_hash = create_coordinate_hash(self.reference_cube) neighbours = np.array([[[0., 0., 0.]]]) altitudes = np.array([0]) latitudes = np.array([0]) longitudes = np.array([0]) wmo_ids = np.array([0]) grid_attributes = ['x_index', 'y_index', 'vertical_displacement'] neighbour_methods = ['nearest'] self.neighbour_cube = build_spotdata_cube( neighbours, 'grid_neighbours', 1, altitudes, latitudes, longitudes, wmo_ids, grid_attributes=grid_attributes, neighbour_methods=neighbour_methods) self.neighbour_cube.attributes['model_grid_hash'] = ( self.diagnostic_cube_hash)
def test_basic(self): """Test the expected hash is returned for a given cube.""" hash_input = set_up_variable_cube(np.zeros((3, 3)).astype(np.float32)) result = create_coordinate_hash(hash_input) expected = "fd40f6d5a8e0a347f181d87bcfd445fa" self.assertIsInstance(result, str) self.assertEqual(result, expected)
def test_global_attribute(self): """Test that a cube is returned with a model_grid_hash that matches that of the global input grids.""" expected = create_coordinate_hash(self.global_orography) plugin = NeighbourSelection() result = plugin.process(self.global_sites, self.global_orography, self.global_land_mask) self.assertIsInstance(result, iris.cube.Cube) self.assertEqual(result.attributes['model_grid_hash'], expected)
def test_region_attribute(self): """Test that a cube is returned with a model_grid_hash that matches that of the regional input grids.""" expected = create_coordinate_hash(self.region_orography) plugin = NeighbourSelection( site_coordinate_system=self.region_projection.as_cartopy_crs(), site_x_coordinate='projection_x_coordinate', site_y_coordinate='projection_y_coordinate') result = plugin.process(self.region_sites, self.region_orography, self.region_land_mask) self.assertIsInstance(result, iris.cube.Cube) self.assertEqual(result.attributes['model_grid_hash'], expected)
def setUp(self): """ Set up cubes for use in testing SpotLapseRateAdjust. Inputs are envisaged as follows: Gridded Lapse rate Orography Temperatures (not used directly) (x DALR) A B C A B C A B C a 2 1 1 1 1 1 270 270 270 b 1 2 1 1 4 1 270 280 270 c 1 1 2 1 1 1 270 270 270 Spot (note the neighbours are identified with the A-C, a-c indices above) Site Temperature Altitude Nearest DZ MinDZ DZ neighbour neighbour 0 280 3 Ac 2 Bb -1 1 270 4 Bb 0 Bb 0 2 280 0 Ca -1 Ca -1 """ # Set up lapse rate cube lapse_rate_data = np.ones(9).reshape(3, 3).astype(np.float32) * DALR lapse_rate_data[0, 2] = 2 * DALR lapse_rate_data[1, 1] = 2 * DALR lapse_rate_data[2, 0] = 2 * DALR self.lapse_rate_cube = set_up_variable_cube(lapse_rate_data, name="lapse_rate", units="K m-1", spatial_grid="equalarea") diagnostic_cube_hash = create_coordinate_hash(self.lapse_rate_cube) # Set up neighbour and spot diagnostic cubes y_coord, x_coord = construct_xy_coords(3, 3, "equalarea") y_coord = y_coord.points x_coord = x_coord.points # neighbours, each group is for a point under two methods, e.g. # [ 0. 0. 0.] is the nearest point to the first spot site, whilst # [ 1. 1. -1.] is the nearest point with minimum height difference. neighbours = np.array([[[0., 0., 2.], [1., 1., -1.]], [[1., 1., 0.], [1., 1., 0.]], [[2., 2., -1.], [2., 2., -1.]]]) altitudes = np.array([3, 4, 0]) latitudes = np.array([y_coord[0], y_coord[1], y_coord[2]]) longitudes = np.array([x_coord[0], x_coord[1], x_coord[2]]) wmo_ids = np.arange(3) grid_attributes = ['x_index', 'y_index', 'vertical_displacement'] neighbour_methods = ['nearest', 'nearest_minimum_dz'] self.neighbour_cube = build_spotdata_cube( neighbours, 'grid_neighbours', 1, altitudes, latitudes, longitudes, wmo_ids, grid_attributes=grid_attributes, neighbour_methods=neighbour_methods) self.neighbour_cube.attributes['model_grid_hash'] = ( diagnostic_cube_hash) time, = iris_time_to_datetime(self.lapse_rate_cube.coord("time")) frt, = iris_time_to_datetime( self.lapse_rate_cube.coord("forecast_reference_time")) time_bounds = None time_coords = construct_scalar_time_coords(time, time_bounds, frt) time_coords = [item[0] for item in time_coords] # This temperature cube is set up with the spot sites having obtained # their temperature values from the nearest grid sites. temperatures_nearest = np.array([280, 270, 280]) self.spot_temperature_nearest = build_spotdata_cube( temperatures_nearest, 'air_temperature', 'K', altitudes, latitudes, longitudes, wmo_ids, scalar_coords=time_coords) self.spot_temperature_nearest.attributes['model_grid_hash'] = ( diagnostic_cube_hash) # This temperature cube is set up with the spot sites having obtained # their temperature values from the nearest minimum vertical # displacment grid sites. The only difference here is for site 0, which # now gets its temperature from Bb (see doc-string above). temperatures_mindz = np.array([270, 270, 280]) self.spot_temperature_mindz = build_spotdata_cube( temperatures_mindz, 'air_temperature', 'K', altitudes, latitudes, longitudes, wmo_ids, scalar_coords=time_coords) self.spot_temperature_mindz.attributes['model_grid_hash'] = ( diagnostic_cube_hash)
def _get_grid_hash(cube): try: cube_hash = cube.attributes['model_grid_hash'] except KeyError: cube_hash = create_coordinate_hash(cube) return cube_hash
def process(self, sites, orography, land_mask): """ Using the constraints provided, find the nearest grid point neighbours to the given spot sites for the model/grid given by the input cubes. Returned is a cube that contains the defining characteristics of the spot sites (e.g. x coordinate, y coordinate, altitude) and the indices of the selected grid point neighbour. Args: sites (list of dict): A list of dictionaries defining the spot sites for which neighbours are to be found. e.g.: [{'altitude': 11.0, 'latitude': 57.867000579833984, 'longitude': -5.632999897003174, 'wmo_id': 3034}] orography (iris.cube.Cube): A cube of orography, used to obtain the grid point altitudes. land_mask (iris.cube.Cube): A land mask cube for the model/grid from which grid point neighbours are being selected. Returns: neighbour_cube (iris.cube.Cube): A cube containing both the spot site information and for each the grid point indices of its nearest neighbour as per the imposed constraints. """ index_nodes = [] # Check if we are dealing with a global grid. self.global_coordinate_system = orography.coord(axis='x').circular # Exclude regional grids with spatial dimensions other than metres. if not self.global_coordinate_system: if not orography.coord(axis='x').units == 'metres': msg = ('Cube spatial coordinates for regional grids must be' 'in metres to match the defined search_radius.') raise ValueError(msg) # Ensure land_mask and orography are on the same grid. if not orography.dim_coords == land_mask.dim_coords: msg = ('Orography and land_mask cubes are not on the same ' 'grid.') raise ValueError(msg) # Enforce x-y coordinate order for input cubes. orography = enforce_coordinate_ordering( orography, [orography.coord(axis='x').name(), orography.coord(axis='y').name()]) land_mask = enforce_coordinate_ordering( land_mask, [land_mask.coord(axis='x').name(), land_mask.coord(axis='y').name()]) # Remap site coordinates on to coordinate system of the model grid. site_x_coords = np.array([site[self.site_x_coordinate] for site in sites]) site_y_coords = np.array([site[self.site_y_coordinate] for site in sites]) site_coords = self._transform_sites_coordinate_system( site_x_coords, site_y_coords, orography) # Exclude any sites falling outside the domain given by the cube and # notify the user. sites, site_coords, site_x_coords, site_y_coords = ( self.check_sites_are_within_domain( sites, site_coords, site_x_coords, site_y_coords, orography)) # Find nearest neighbour point using quick iris method. nearest_indices = self.get_nearest_indices(site_coords, orography) # Create an array containing site altitudes, using the nearest point # orography height for any that are unset. site_altitudes = np.array([site.get(self.site_altitude, None) for site in sites]) site_altitudes = np.where(np.isnan(site_altitudes.astype(float)), orography.data[tuple(nearest_indices.T)], site_altitudes) # If further constraints are being applied, build a KD Tree which # includes points filtered by constraint. if self.land_constraint or self.minimum_dz: # Build the KDTree, an internal test for the land_constraint checks # whether to exclude sea points from the tree. tree, index_nodes = self.build_KDTree(land_mask) # Site coordinates made cartesian for global coordinate system if self.global_coordinate_system: site_coords = self.geocentric_cartesian( orography, site_coords[:, 0], site_coords[:, 1]) if not self.minimum_dz: # Query the tree for the nearest neighbour, in this case a land # neighbour is returned along with the distance to it. distances, node_indices = tree.query([site_coords]) # Look up the grid coordinates that correspond to the tree node land_neighbour_indices, = index_nodes[node_indices] # Use the found land neighbour if it is within the # search_radius, otherwise use the nearest neighbour. distances = np.array([distances[0], distances[0]]).T nearest_indices = np.where(distances < self.search_radius, land_neighbour_indices, nearest_indices) else: # Query the tree for self.node_limit nearby neighbours. distances, node_indices = tree.query( [site_coords], distance_upper_bound=self.search_radius, k=self.node_limit) # Loop over the sites and for each choose the returned # neighbour with the minimum vertical displacement. for index, (distance, indices) in enumerate(zip( distances[0], node_indices[0])): grid_point = self.select_minimum_dz( orography, site_altitudes[index], index_nodes, distance, indices) # None is returned if the tree query returned no neighbours # within the search radius. if grid_point is not None: nearest_indices[index] = grid_point # Calculate the vertical displacements between the chosen grid point # and the spot site. vertical_displacements = (site_altitudes - orography.data[tuple(nearest_indices.T)]) # Create a list of WMO IDs if available. These are stored as strings # to accommodate the use of 'None' for unset IDs. wmo_ids = [str(site.get('wmo_id', None)) for site in sites] # Construct a name to describe the neighbour finding method employed method_name = self.neighbour_finding_method_name() # Create an array of indices and displacements to return data = np.stack((nearest_indices[:, 0], nearest_indices[:, 1], vertical_displacements), axis=1) data = np.expand_dims(data, 1).astype(np.float32) # Create a cube of neighbours neighbour_cube = build_spotdata_cube( data, 'grid_neighbours', 1, site_altitudes.astype(np.float32), site_y_coords.astype(np.float32), site_x_coords.astype(np.float32), wmo_ids, neighbour_methods=[method_name], grid_attributes=['x_index', 'y_index', 'vertical_displacement']) # Add a hash attribute based on the model grid to ensure the neighbour # cube is only used with a compatible grid. grid_hash = create_coordinate_hash(orography) neighbour_cube.attributes['model_grid_hash'] = grid_hash return neighbour_cube
def setUp(self): """ Set up cubes and sitelists for use in testing SpotExtraction. The envisaged scenario is an island (1) surrounded by water (0). Land-sea Orography Diagnostic 0 0 0 0 0 0 0 0 0 0 0 1 2 3 4 0 1 1 1 0 0 1 2 1 0 5 6 7 8 9 0 1 1 1 0 0 2 3 2 0 10 11 12 13 14 0 1 1 1 0 0 1 2 1 0 15 16 17 18 19 0 0 0 0 0 0 0 0 0 0 20 21 22 23 24 """ # Set up diagnostic data cube and neighbour cubes. diagnostic_data = np.arange(25).reshape(5, 5) xcoord = iris.coords.DimCoord( np.linspace(0, 40, 5), standard_name='longitude', units='degrees') ycoord = iris.coords.DimCoord( np.linspace(0, 40, 5), standard_name='latitude', units='degrees') attributes = { 'mosg__grid_domain': 'global', 'mosg__grid_type': 'standard'} diagnostic_cube_xy = iris.cube.Cube( diagnostic_data, standard_name="air_temperature", units='K', dim_coords_and_dims=[(ycoord, 1), (xcoord, 0)], attributes=attributes) diagnostic_cube_yx = iris.cube.Cube( diagnostic_data.T, standard_name="air_temperature", units='K', dim_coords_and_dims=[(ycoord, 0), (xcoord, 1)], attributes=attributes) diagnostic_cube_hash = create_coordinate_hash(diagnostic_cube_yx) # neighbours, each group is for a point under two methods, e.g. # [ 0. 0. 0.] is the nearest point to the first spot site, whilst # [ 1. 1. -1.] is the nearest land point to the same site. neighbours = np.array([[[0., 0., 0.], [1., 1., -1.]], [[0., 0., -1.], [1., 1., 0.]], [[2., 2., 0.], [2., 2., 0.]], [[2., 2., 1.], [2., 2., 1.]]]) altitudes = np.array([0, 1, 3, 2]) latitudes = np.array([10, 10, 20, 20]) longitudes = np.array([10, 10, 20, 20]) wmo_ids = np.arange(4) grid_attributes = ['x_index', 'y_index', 'vertical_displacement'] neighbour_methods = ['nearest', 'nearest_land'] neighbour_cube = build_spotdata_cube( neighbours, 'grid_neighbours', 1, altitudes, latitudes, longitudes, wmo_ids, grid_attributes=grid_attributes, neighbour_methods=neighbour_methods) neighbour_cube.attributes['model_grid_hash'] = diagnostic_cube_hash coordinate_cube = neighbour_cube.extract( iris.Constraint(neighbour_selection_method_name='nearest') & iris.Constraint(grid_attributes_key=['x_index', 'y_index'])) coordinate_cube.data = np.rint(coordinate_cube.data).astype(int) self.latitudes = latitudes self.longitudes = longitudes self.diagnostic_cube_xy = diagnostic_cube_xy self.diagnostic_cube_yx = diagnostic_cube_yx self.neighbours = neighbours self.neighbour_cube = neighbour_cube self.coordinate_cube = coordinate_cube