def test_different_coord_systems(self): u, v = uv_cubes() v.coord("grid_latitude").coord_system = iris.coord_systems.GeogCS(1) with self.assertRaisesRegex( ValueError, "Coordinates differ between u and v cubes" ): rotate_winds(u, v, iris.coord_systems.OSGB())
def test_rotated_to_unrotated(self): # Check ability to use 2d coords as input. u, v = uv_cubes() ut, vt = rotate_winds(u, v, iris.coord_systems.GeogCS(6371229)) # Remove grid lat and lon, leaving 2d projection coords. ut.remove_coord('grid_latitude') vt.remove_coord('grid_latitude') ut.remove_coord('grid_longitude') vt.remove_coord('grid_longitude') # Change back. orig_cs = u.coord('grid_latitude').coord_system res_u, res_v = rotate_winds(ut, vt, orig_cs) # Check data values - limited accuracy due to numerical approx. self.assertArrayAlmostEqual(res_u.data, u.data, decimal=3) self.assertArrayAlmostEqual(res_v.data, v.data, decimal=3) # Check coords locations. x2d, y2d = np.meshgrid(u.coord('grid_longitude').points, u.coord('grid_latitude').points) # Shift longitude from 0 to 360 -> -180 to 180. x2d = np.where(x2d > 180, x2d - 360, x2d) res_x = res_u.coord('projection_x_coordinate', coord_system=orig_cs).points res_y = res_u.coord('projection_y_coordinate', coord_system=orig_cs).points self.assertArrayAlmostEqual(res_x, x2d) self.assertArrayAlmostEqual(res_y, y2d) res_x = res_v.coord('projection_x_coordinate', coord_system=orig_cs).points res_y = res_v.coord('projection_y_coordinate', coord_system=orig_cs).points self.assertArrayAlmostEqual(res_x, x2d) self.assertArrayAlmostEqual(res_y, y2d)
def test_rotated_to_unrotated(self): # Check ability to use 2d coords as input. u, v = uv_cubes() ut, vt = rotate_winds(u, v, iris.coord_systems.GeogCS(6371229)) # Remove grid lat and lon, leaving 2d projection coords. ut.remove_coord("grid_latitude") vt.remove_coord("grid_latitude") ut.remove_coord("grid_longitude") vt.remove_coord("grid_longitude") # Change back. orig_cs = u.coord("grid_latitude").coord_system res_u, res_v = rotate_winds(ut, vt, orig_cs) # Check data values - limited accuracy due to numerical approx. self.assertArrayAlmostEqual(res_u.data, u.data, decimal=3) self.assertArrayAlmostEqual(res_v.data, v.data, decimal=3) # Check coords locations. x2d, y2d = np.meshgrid( u.coord("grid_longitude").points, u.coord("grid_latitude").points) # Shift longitude from 0 to 360 -> -180 to 180. x2d = np.where(x2d > 180, x2d - 360, x2d) res_x = res_u.coord("projection_x_coordinate", coord_system=orig_cs).points res_y = res_u.coord("projection_y_coordinate", coord_system=orig_cs).points self.assertArrayAlmostEqual(res_x, x2d) self.assertArrayAlmostEqual(res_y, y2d) res_x = res_v.coord("projection_x_coordinate", coord_system=orig_cs).points res_y = res_v.coord("projection_y_coordinate", coord_system=orig_cs).points self.assertArrayAlmostEqual(res_x, x2d) self.assertArrayAlmostEqual(res_y, y2d)
def test_dim_mapping(self): x = np.linspace(311.9, 391.1, 3) y = np.linspace(-23.6, 24.8, 3) u, v = uv_cubes(x, y) v.transpose() with self.assertRaisesRegex(ValueError, "Dimension mapping"): rotate_winds(u, v, iris.coord_systems.OSGB())
def test_dim_mapping(self): x = np.linspace(311.9, 391.1, 3) y = np.linspace(-23.6, 24.8, 3) u, v = uv_cubes(x, y) v.transpose() with self.assertRaisesRegexp(ValueError, 'Dimension mapping'): rotate_winds(u, v, iris.coord_systems.OSGB())
def test_different_shape(self): x = np.linspace(311.9, 391.1, 6) y = np.linspace(-23.6, 24.8, 5) u, _ = uv_cubes(x, y) _, v = uv_cubes(x[:-1], y) with self.assertRaisesRegexp(ValueError, 'same shape'): rotate_winds(u, v, iris.coord_systems.OSGB())
def test_different_shape(self): x = np.linspace(311.9, 391.1, 6) y = np.linspace(-23.6, 24.8, 5) u, _ = uv_cubes(x, y) _, v = uv_cubes(x[:-1], y) with self.assertRaisesRegex(ValueError, "same shape"): rotate_winds(u, v, iris.coord_systems.OSGB())
def test_different_xy_coord_systems(self): u, v = uv_cubes() u.coord('grid_latitude').coord_system = iris.coord_systems.GeogCS(1) v.coord('grid_latitude').coord_system = iris.coord_systems.GeogCS(1) with self.assertRaisesRegexp( ValueError, 'Coordinate systems of x and y coordinates differ'): rotate_winds(u, v, iris.coord_systems.OSGB())
def test_new_coords(self): u, v = self._uv_cubes_limited_extent() x = u.coord('grid_longitude').points y = u.coord('grid_latitude').points x2d, y2d = np.meshgrid(x, y) src_crs = ccrs.RotatedPole(pole_longitude=177.5, pole_latitude=37.5) tgt_crs = ccrs.OSGB() xyz_tran = tgt_crs.transform_points(src_crs, x2d, y2d) ut, vt = rotate_winds(u, v, iris.coord_systems.OSGB()) points = xyz_tran[..., 0].reshape(x2d.shape) expected_x = AuxCoord(points, standard_name='projection_x_coordinate', units='m', coord_system=iris.coord_systems.OSGB()) self.assertEqual(ut.coord('projection_x_coordinate'), expected_x) self.assertEqual(vt.coord('projection_x_coordinate'), expected_x) points = xyz_tran[..., 1].reshape(y2d.shape) expected_y = AuxCoord(points, standard_name='projection_y_coordinate', units='m', coord_system=iris.coord_systems.OSGB()) self.assertEqual(ut.coord('projection_y_coordinate'), expected_y) self.assertEqual(vt.coord('projection_y_coordinate'), expected_y)
def test_rotated_to_osgb(self): # Rotated Pole data with large extent. x = np.linspace(311.9, 391.1, 10) y = np.linspace(-23.6, 24.8, 8) u, v = uv_cubes(x, y) ut, vt = rotate_winds(u, v, iris.coord_systems.OSGB()) # Ensure cells with discrepancies in magnitude are masked. self.assertTrue(ma.isMaskedArray(ut.data)) self.assertTrue(ma.isMaskedArray(vt.data)) # Snapshot of mask with fixed tolerance of atol=2e-3 expected_mask = np.array([[1, 1, 1, 0, 0, 0, 0, 0, 0, 1], [1, 1, 1, 0, 0, 0, 0, 0, 0, 1], [1, 1, 1, 1, 0, 0, 0, 0, 1, 1], [1, 1, 1, 1, 0, 0, 0, 0, 1, 1], [1, 1, 1, 1, 0, 0, 0, 0, 1, 1], [1, 1, 1, 1, 1, 0, 0, 1, 1, 1], [1, 1, 1, 1, 1, 0, 0, 1, 1, 1], [1, 1, 1, 1, 1, 0, 0, 1, 1, 1]], np.bool) self.assertArrayEqual(expected_mask, ut.data.mask) self.assertArrayEqual(expected_mask, vt.data.mask) # Check unmasked values have sufficiently small error in mag. expected_mag = np.sqrt(u.data**2 + v.data**2) # Use underlying data to ignore mask in calculation. res_mag = np.sqrt(ut.data.data**2 + vt.data.data**2) # Calculate percentage error (note there are no zero magnitudes # so we can divide safely). anom = 100.0 * np.abs(res_mag - expected_mag) / expected_mag self.assertTrue(anom[~ut.data.mask].max() < 0.1)
def _check_rotated_to_true(self, u_rot, v_rot, target_cs, **kwds): # Run test calculation (numeric). u_true, v_true = rotate_winds(u_rot, v_rot, target_cs) # Perform same calculation via the reference method (equations). cs_rot = u_rot.coord("grid_longitude").coord_system pole_lat = cs_rot.grid_north_pole_latitude pole_lon = cs_rot.grid_north_pole_longitude rotated_lons = u_rot.coord("grid_longitude").points rotated_lats = u_rot.coord("grid_latitude").points rotated_lons_2d, rotated_lats_2d = np.meshgrid(rotated_lons, rotated_lats) rotated_u, rotated_v = u_rot.data, v_rot.data u_ref, v_ref = self._unrotate_equation( rotated_lons_2d, rotated_lats_2d, rotated_u, rotated_v, pole_lon, pole_lat, ) # Check that all the numerical results are within given tolerances. self.assertArrayAllClose(u_true.data, u_ref, **kwds) self.assertArrayAllClose(v_true.data, v_ref, **kwds)
def test_name(self): u, v = self._uv_cubes_limited_extent() u.rename("bob") v.rename("alice") ut, vt = rotate_winds(u, v, iris.coord_systems.OSGB()) self.assertEqual(ut.name(), "transformed_" + u.name()) self.assertEqual(vt.name(), "transformed_" + v.name())
def test_new_coords(self): u, v = self._uv_cubes_limited_extent() x = u.coord("grid_longitude").points y = u.coord("grid_latitude").points x2d, y2d = np.meshgrid(x, y) src_crs = ccrs.RotatedPole(pole_longitude=177.5, pole_latitude=37.5) tgt_crs = ccrs.OSGB() xyz_tran = tgt_crs.transform_points(src_crs, x2d, y2d) ut, vt = rotate_winds(u, v, iris.coord_systems.OSGB()) points = xyz_tran[..., 0].reshape(x2d.shape) expected_x = AuxCoord( points, standard_name="projection_x_coordinate", units="m", coord_system=iris.coord_systems.OSGB(), ) self.assertEqual(ut.coord("projection_x_coordinate"), expected_x) self.assertEqual(vt.coord("projection_x_coordinate"), expected_x) points = xyz_tran[..., 1].reshape(y2d.shape) expected_y = AuxCoord( points, standard_name="projection_y_coordinate", units="m", coord_system=iris.coord_systems.OSGB(), ) self.assertEqual(ut.coord("projection_y_coordinate"), expected_y) self.assertEqual(vt.coord("projection_y_coordinate"), expected_y)
def test_orig_coords(self): u, v = self._uv_cubes_limited_extent() ut, vt = rotate_winds(u, v, iris.coord_systems.OSGB()) self.assertEqual(u.coord('grid_latitude'), ut.coord('grid_latitude')) self.assertEqual(v.coord('grid_latitude'), vt.coord('grid_latitude')) self.assertEqual(u.coord('grid_longitude'), ut.coord('grid_longitude')) self.assertEqual(v.coord('grid_longitude'), vt.coord('grid_longitude'))
def test_name(self): u, v = self._uv_cubes_limited_extent() u.rename('bob') v.rename('alice') ut, vt = rotate_winds(u, v, iris.coord_systems.OSGB()) self.assertEqual(ut.name(), 'transformed_' + u.name()) self.assertEqual(vt.name(), 'transformed_' + v.name())
def test_xy_dimensionality(self): u, v = uv_cubes() # Replace 1d lat with 2d lat. x = u.coord('grid_longitude').points y = u.coord('grid_latitude').points x2d, y2d = np.meshgrid(x, y) lat_2d = AuxCoord(y2d, 'grid_latitude', units='degrees', coord_system=u.coord('grid_latitude').coord_system) for cube in (u, v): cube.remove_coord('grid_latitude') cube.add_aux_coord(lat_2d.copy(), (0, 1)) with self.assertRaisesRegexp( ValueError, 'x and y coordinates must have the same number of dimensions'): rotate_winds(u, v, iris.coord_systems.OSGB())
def test_xy_dimensionality(self): u, v = uv_cubes() # Replace 1d lat with 2d lat. x = u.coord('grid_longitude').points y = u.coord('grid_latitude').points x2d, y2d = np.meshgrid(x, y) lat_2d = AuxCoord(y2d, 'grid_latitude', units='degrees', coord_system=u.coord('grid_latitude').coord_system) for cube in (u, v): cube.remove_coord('grid_latitude') cube.add_aux_coord(lat_2d.copy(), (0, 1)) with self.assertRaisesRegex( ValueError, 'x and y coordinates must have the same number of dimensions'): rotate_winds(u, v, iris.coord_systems.OSGB())
def test_rotated_to_osgb(self): # Rotated Pole data with large extent. x = np.linspace(311.9, 391.1, 10) y = np.linspace(-23.6, 24.8, 8) u, v = uv_cubes(x, y) ut, vt = rotate_winds(u, v, iris.coord_systems.OSGB()) # Ensure cells with discrepancies in magnitude are masked. self.assertTrue(ma.isMaskedArray(ut.data)) self.assertTrue(ma.isMaskedArray(vt.data)) # Snapshot of mask with fixed tolerance of atol=2e-3 expected_mask = np.array( [[1, 1, 1, 0, 0, 0, 0, 0, 0, 1], [1, 1, 1, 0, 0, 0, 0, 0, 0, 1], [1, 1, 1, 1, 0, 0, 0, 0, 1, 1], [1, 1, 1, 1, 0, 0, 0, 0, 1, 1], [1, 1, 1, 1, 0, 0, 0, 0, 1, 1], [1, 1, 1, 1, 1, 0, 0, 1, 1, 1], [1, 1, 1, 1, 1, 0, 0, 1, 1, 1], [1, 1, 1, 1, 1, 0, 0, 1, 1, 1]], np.bool) self.assertArrayEqual(expected_mask, ut.data.mask) self.assertArrayEqual(expected_mask, vt.data.mask) # Check unmasked values have sufficiently small error in mag. expected_mag = np.sqrt(u.data**2 + v.data**2) # Use underlying data to ignore mask in calculation. res_mag = np.sqrt(ut.data.data**2 + vt.data.data**2) # Calculate percentage error (note there are no zero magnitudes # so we can divide safely). anom = 100.0 * np.abs(res_mag - expected_mag) / expected_mag self.assertTrue(anom[~ut.data.mask].max() < 0.1)
def test_orig_coords(self): u, v = self._uv_cubes_limited_extent() ut, vt = rotate_winds(u, v, iris.coord_systems.OSGB()) self.assertEqual(u.coord("grid_latitude"), ut.coord("grid_latitude")) self.assertEqual(v.coord("grid_latitude"), vt.coord("grid_latitude")) self.assertEqual(u.coord("grid_longitude"), ut.coord("grid_longitude")) self.assertEqual(v.coord("grid_longitude"), vt.coord("grid_longitude"))
def unrotate_uv(u, v, target_cs=None, remove_aux_xy=True): """ Rotate u- and v-winds to a given CS (Geog by default) and remove auxiliary coordinates created automatically by `rotate_winds()` function """ if target_cs is None: target_cs = iris.coord_systems.GeogCS(EARTH_RADIUS) uv = rotate_winds(u, v, target_cs) if remove_aux_xy: [cube.remove_coord(i) for i in ('projection_x_coordinate', 'projection_y_coordinate') for cube in uv] return uv
def test_new_coords_transposed(self): u, v = self._uv_cubes_limited_extent() # Transpose cubes so that cube is in xy order rather than the # typical yx order of meshgrid. u.transpose() v.transpose() x = u.coord("grid_longitude").points y = u.coord("grid_latitude").points x2d, y2d = np.meshgrid(x, y) src_crs = ccrs.RotatedPole(pole_longitude=177.5, pole_latitude=37.5) tgt_crs = ccrs.OSGB() xyz_tran = tgt_crs.transform_points(src_crs, x2d, y2d) ut, vt = rotate_winds(u, v, iris.coord_systems.OSGB()) points = xyz_tran[..., 0].reshape(x2d.shape) expected_x = AuxCoord( points, standard_name="projection_x_coordinate", units="m", coord_system=iris.coord_systems.OSGB(), ) self.assertEqual(ut.coord("projection_x_coordinate"), expected_x) self.assertEqual(vt.coord("projection_x_coordinate"), expected_x) points = xyz_tran[..., 1].reshape(y2d.shape) expected_y = AuxCoord( points, standard_name="projection_y_coordinate", units="m", coord_system=iris.coord_systems.OSGB(), ) self.assertEqual(ut.coord("projection_y_coordinate"), expected_y) self.assertEqual(vt.coord("projection_y_coordinate"), expected_y) # Check dim mapping for 2d coords is yx. expected_dims = u.coord_dims("grid_latitude") + u.coord_dims( "grid_longitude" ) self.assertEqual( ut.coord_dims("projection_x_coordinate"), expected_dims ) self.assertEqual( ut.coord_dims("projection_y_coordinate"), expected_dims ) self.assertEqual( vt.coord_dims("projection_x_coordinate"), expected_dims ) self.assertEqual( vt.coord_dims("projection_y_coordinate"), expected_dims )
def test_data_values(self): u, v = self._uv_cubes_limited_extent() # Slice out 4 points that lie in and outside OSGB extent. u = u[1:3, 3:5] v = v[1:3, 3:5] ut, vt = rotate_winds(u, v, iris.coord_systems.OSGB()) # Values precalculated and checked. expected_ut_data = np.array([[0.16285514, 0.35323639], [1.82650698, 2.62455840]]) expected_vt_data = np.array([[19.88979966, 19.01921346], [19.88018847, 19.01424281]]) # Compare u and v data values against previously calculated values. self.assertArrayAllClose(ut.data, expected_ut_data, rtol=1e-5) self.assertArrayAllClose(vt.data, expected_vt_data, rtol=1e-5)
def get_geographic_coordinates(u, v, x, y, cs): """Convert winds and positions from a rotated grid to an unrotated grid Args: u, v (np.Array): Wind fields on rotated grid x, y (np.Array): Trajectory longitude and latitude positions on rotated grid cs (iris.coord_systems.CoordSystem): The coordinate system of the rotated longitude/latitude grid Returns u_wind, v_wind (np.Array): Wind fields on unrotated grid lon, lat (np.Array): Trajectory positions """ # Place input information in to iris cubes to perform to rotation rlon = AuxCoord(x, standard_name='grid_longitude', units='degrees', coord_system=cs) rlat = AuxCoord(y, standard_name='grid_latitude', units='degrees', coord_system=cs) u_array, v_array = [], [] for n in range(len(u)): u_array.append(u) v_array.append(v) u = np.array(u_array) v = np.array(v_array) u = Cube(u, standard_name='x_wind', units='m s-1', aux_coords_and_dims=[(rlon, 0), (rlat, 1)]) v = Cube(v, standard_name='y_wind', units='m s-1', aux_coords_and_dims=[(rlon, 0), (rlat, 1)]) # Calculate unrotated winds and postitions u, v = rotate_winds(u, v, GeogCS(a)) # Extract information from the unrotated cubes u_wind, v_wind, lon, lat = [], [], [], [] lons = u.coord('projection_x_coordinate').points lats = u.coord('projection_y_coordinate').points for n in range(len(x)): u_wind.append(u.data[n,n]) v_wind.append(v.data[n,n]) lon.append(lons[n, n]) lat.append(lats[n, n]) u_wind = np.array(u_wind) v_wind = np.array(v_wind) lon = np.array(lon) lat = np.array(lat) return u_wind, v_wind, lon, lat
def test_nd_data(self): u2d, y2d = self._uv_cubes_limited_extent() u, v = uv_cubes_3d(u2d) u = u[:, 1:3, 3:5] v = v[:, 1:3, 3:5] ut, vt = rotate_winds(u, v, iris.coord_systems.OSGB()) # Values precalculated and checked (as test_data_values above), # then scaled by factor [1, 2, 3] along 0th dim (see uv_cubes_3d()). expected_ut_data = np.array([[0.16285514, 0.35323639], [1.82650698, 2.62455840]]) expected_vt_data = np.array([[19.88979966, 19.01921346], [19.88018847, 19.01424281]]) factor = np.array([1, 2, 3]).reshape(3, 1, 1) expected_ut_data = factor * expected_ut_data expected_vt_data = factor * expected_vt_data # Compare u and v data values against previously calculated values. self.assertArrayAlmostEqual(ut.data, expected_ut_data) self.assertArrayAlmostEqual(vt.data, expected_vt_data)
def test_new_coords_transposed(self): u, v = self._uv_cubes_limited_extent() # Transpose cubes so that cube is in xy order rather than the # typical yx order of meshgrid. u.transpose() v.transpose() x = u.coord('grid_longitude').points y = u.coord('grid_latitude').points x2d, y2d = np.meshgrid(x, y) src_crs = ccrs.RotatedPole(pole_longitude=177.5, pole_latitude=37.5) tgt_crs = ccrs.OSGB() xyz_tran = tgt_crs.transform_points(src_crs, x2d, y2d) ut, vt = rotate_winds(u, v, iris.coord_systems.OSGB()) points = xyz_tran[..., 0].reshape(x2d.shape) expected_x = AuxCoord(points, standard_name='projection_x_coordinate', units='m', coord_system=iris.coord_systems.OSGB()) self.assertEqual(ut.coord('projection_x_coordinate'), expected_x) self.assertEqual(vt.coord('projection_x_coordinate'), expected_x) points = xyz_tran[..., 1].reshape(y2d.shape) expected_y = AuxCoord(points, standard_name='projection_y_coordinate', units='m', coord_system=iris.coord_systems.OSGB()) self.assertEqual(ut.coord('projection_y_coordinate'), expected_y) self.assertEqual(vt.coord('projection_y_coordinate'), expected_y) # Check dim mapping for 2d coords is yx. expected_dims = (u.coord_dims('grid_latitude') + u.coord_dims('grid_longitude')) self.assertEqual(ut.coord_dims('projection_x_coordinate'), expected_dims) self.assertEqual(ut.coord_dims('projection_y_coordinate'), expected_dims) self.assertEqual(vt.coord_dims('projection_x_coordinate'), expected_dims) self.assertEqual(vt.coord_dims('projection_y_coordinate'), expected_dims)
def _check_rotated_to_true(self, u_rot, v_rot, target_cs, **kwds): # Run test calculation (numeric). u_true, v_true = rotate_winds(u_rot, v_rot, target_cs) # Perform same calculation via the reference method (equations). cs_rot = u_rot.coord('grid_longitude').coord_system pole_lat = cs_rot.grid_north_pole_latitude pole_lon = cs_rot.grid_north_pole_longitude rotated_lons = u_rot.coord('grid_longitude').points rotated_lats = u_rot.coord('grid_latitude').points rotated_lons_2d, rotated_lats_2d = np.meshgrid( rotated_lons, rotated_lats) rotated_u, rotated_v = u_rot.data, v_rot.data u_ref, v_ref = self._unrotate_equation(rotated_lons_2d, rotated_lats_2d, rotated_u, rotated_v, pole_lon, pole_lat) # Check that all the numerical results are within given tolerances. self.assertArrayAllClose(u_true.data, u_ref, **kwds) self.assertArrayAllClose(v_true.data, v_ref, **kwds)
def calc_true_north_offset(reference_cube: Cube) -> ndarray: """ Calculate the angles between grid North and true North, as a matrix of values on the grid of the input reference cube. Args: reference_cube: 2D cube on grid for which "north" is required. Provides both coordinate system (reference_cube.coord_system()) and template spatial grid on which the angle adjustments should be provided. Returns: Angle in radians by which wind direction wrt true North at each point must be rotated to be relative to grid North. """ reference_x_coord = reference_cube.coord(axis="x") reference_y_coord = reference_cube.coord(axis="y") # find corners of reference_cube grid in lat / lon coordinates latlon = [ GLOBAL_CRS.as_cartopy_crs().transform_point( reference_x_coord.points[i], reference_y_coord.points[j], reference_cube.coord_system().as_cartopy_crs(), ) for i in [0, -1] for j in [0, -1] ] latlon = np.array(latlon).T.tolist() # define lat / lon coordinates to cover the reference_cube grid at an # equivalent resolution lat_points = np.linspace( np.floor(min(latlon[1])), np.ceil(max(latlon[1])), len(reference_y_coord.points), ) lon_points = np.linspace( np.floor(min(latlon[0])), np.ceil(max(latlon[0])), len(reference_x_coord.points), ) lat_coord = DimCoord( lat_points, "latitude", units="degrees", coord_system=GLOBAL_CRS ) lon_coord = DimCoord( lon_points, "longitude", units="degrees", coord_system=GLOBAL_CRS ) # define a unit vector wind towards true North over the lat / lon grid udata = np.zeros(reference_cube.shape, dtype=np.float32) vdata = np.ones(reference_cube.shape, dtype=np.float32) ucube_truenorth = Cube( udata, "grid_eastward_wind", dim_coords_and_dims=[(lat_coord, 0), (lon_coord, 1)], ) vcube_truenorth = Cube( vdata, "grid_northward_wind", dim_coords_and_dims=[(lat_coord, 0), (lon_coord, 1)], ) # rotate unit vector onto reference_cube coordinate system ucube, vcube = rotate_winds( ucube_truenorth, vcube_truenorth, reference_cube.coord_system() ) # unmask and regrid rotated winds onto reference_cube grid ucube.data = ucube.data.data ucube = ucube.regrid(reference_cube, Linear()) vcube.data = vcube.data.data vcube = vcube.regrid(reference_cube, Linear()) # ratio of u to v winds is the tangent of the angle which is the # true North to grid North rotation angle_adjustment = np.arctan2(ucube.data, vcube.data) return angle_adjustment
def test_rotated_to_unrotated(self): # Suffiently accurate so that no mask is introduced. u, v = uv_cubes() ut, vt = rotate_winds(u, v, iris.coord_systems.GeogCS(6371229)) self.assertFalse(ma.isMaskedArray(ut.data)) self.assertFalse(ma.isMaskedArray(vt.data))
def test_magnitude_preservation(self): u, v = self._uv_cubes_limited_extent() ut, vt = rotate_winds(u, v, iris.coord_systems.OSGB()) orig_sq_mag = u.data**2 + v.data**2 res_sq_mag = ut.data**2 + vt.data**2 self.assertArrayAllClose(orig_sq_mag, res_sq_mag, rtol=5e-4)
def _regrid_and_populate(self, temperature, humidity, pressure, uwind, vwind, topography): """ Regrids input variables onto the high resolution orography field, then populates the class instance with regridded variables before converting to SI units. Also calculates V.gradZ as a class member. Args: temperature (iris.cube.Cube): Temperature at top of boundary layer humidity (iris.cube.Cube): Relative humidity at top of boundary layer pressure (iris.cube.Cube): Pressure at top of boundary layer uwind (iris.cube.Cube): Positive eastward wind vector component at top of boundary layer vwind (iris.cube.Cube): Positive northward wind vector component at top of boundary layer topography (iris.cube.Cube): Height of topography above sea level on 1 km UKPP domain grid """ # convert topography grid, datatype and units for axis in ['x', 'y']: topography = sort_coord_in_cube(topography, topography.coord(axis=axis)) topography = enforce_coordinate_ordering(topography, [ topography.coord(axis='y').name(), topography.coord(axis='x').name() ]) self.topography = topography.copy( data=topography.data.astype(np.float32)) self.topography.convert_units('m') # rotate winds try: uwind, vwind = rotate_winds(uwind, vwind, topography.coord_system()) except ValueError as err: if 'Duplicate coordinates are not permitted' in str(err): # ignore error raised if uwind and vwind do not need rotating pass else: raise ValueError(str(err)) else: # remove auxiliary spatial coordinates from rotated winds for cube in [uwind, vwind]: for axis in ['x', 'y']: cube.remove_coord(cube.coord(axis=axis, dim_coords=False)) # regrid and convert input variables self.temperature = self._regrid_variable(temperature, 'kelvin') self.humidity = self._regrid_variable(humidity, '1') self.pressure = self._regrid_variable(pressure, 'Pa') self.uwind = self._regrid_variable(uwind, 'm s-1') self.vwind = self._regrid_variable(vwind, 'm s-1') # calculate orography gradients gradx, grady = self._orography_gradients() # calculate v.gradZ self.vgradz = (np.multiply(gradx.data, self.uwind.data) + np.multiply(grady.data, self.vwind.data))