-
Notifications
You must be signed in to change notification settings - Fork 0
added masking of erroneous values #13
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -858,6 +858,45 @@ def _transform_distance_vectors(src_crs, x, y, u_dist, v_dist, tgt_crs): | |
| return u2_dist, v2_dist | ||
|
|
||
|
|
||
| def _transform_distance_vectors_tolerance_mask(src_crs, x, y, tgt_crs): | ||
| """ | ||
| Return a mask that can be applied to data array to mask elements | ||
| where the magnitude of vectors are not preserved due to numerical | ||
| errors introduced by the tranformation between coordinate systems. | ||
|
|
||
| Args: | ||
| * src_crs (`cartopy.crs.Projection`): | ||
| The source coordinate reference systems. | ||
| * x, y (array): | ||
| Locations of each vector defined in 'src_crs'. | ||
| * tgt_crs (`cartopy.crs.Projection`): | ||
| The target coordinate reference systems. | ||
|
|
||
| Returns: | ||
| 2d boolean array that is the same shape as x and y. | ||
|
|
||
| """ | ||
| if x.shape != y.shape: | ||
| raise ValueError('Arrays do not have matching shapes. ' | ||
| 'x.shape is {}, y.shape is {}.'.format(x.shape, | ||
| y.shape)) | ||
| ones = np.ones(x.shape) | ||
| zeros = np.zeros(x.shape) | ||
| u_one_t, v_zero_t = _transform_distance_vectors(src_crs, x, y, | ||
| ones, zeros, tgt_crs) | ||
| u_zero_t, v_one_t = _transform_distance_vectors(src_crs, x, y, | ||
| zeros, ones, tgt_crs) | ||
| # Squared magnitudes should be equal to one within acceptable tolerance. | ||
| # A value of atol=2e-3 is used, which corresponds to a change in magnitude | ||
| # of approximately 0.1%. | ||
| sqmag_1_0 = u_one_t**2 + v_zero_t**2 | ||
| sqmag_0_1 = u_zero_t**2 + v_one_t**2 | ||
| mask = np.logical_not( | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think at this point we may also need to allow for possible NaNs in the transformed vectors.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fortunately |
||
| np.logical_and(np.isclose(sqmag_1_0, ones, atol=2e-3), | ||
| np.isclose(sqmag_0_1, ones, atol=2e-3))) | ||
| return mask | ||
|
|
||
|
|
||
| def rotate_winds(u_cube, v_cube, target_cs): | ||
| """ | ||
| Transform wind vectors to a different coordinate system. | ||
|
|
@@ -902,12 +941,13 @@ def rotate_winds(u_cube, v_cube, target_cs): | |
| The names of the output cubes are those of the inputs, prefixed with | ||
| 'transformed\_' (e.g. 'transformed_x_wind'). | ||
|
|
||
| .. note:: | ||
| .. warning:: | ||
|
|
||
| Conversion between rotated-pole and non-rotated systems can be | ||
| expressed analytically. However, this function always uses a numerical | ||
| approach. | ||
|
|
||
| approach. In locations where this numerical approach does not preserve | ||
| magnitude to an accuracy of 0.1%, the corresponding elements of the | ||
| returned cubes will be masked. | ||
|
|
||
| """ | ||
| # Check u_cube and v_cube have the same shape. We iterate through | ||
|
|
@@ -975,12 +1015,27 @@ def rotate_winds(u_cube, v_cube, target_cs): | |
| else: | ||
| dims = x_dims | ||
|
|
||
| # Transpose x, y 2d arrays to match the order in cube's data | ||
| # array so that x, y and the sliced data all line up. | ||
| if dims[0] > dims[1]: | ||
| x = x.transpose() | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not convinced this is correct.. But when when assign these back into the cube, we just use 'dims' again, which we have not swapped around ... ) So it seems to me we may not need to make this change at all.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good catch @pp-mo. This was a last minute thing I noticed relating to the data, but coords will need looking at too. I'll give it some thought.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've given it thought and I'm now happy. The transpose is necessary as mesh grid will always give yx ordering. |
||
| y = y.transpose() | ||
|
|
||
| # Create resulting cubes. | ||
| ut_cube = u_cube.copy() | ||
| vt_cube = v_cube.copy() | ||
| ut_cube.rename('transformed_{}'.format(u_cube.name())) | ||
| vt_cube.rename('transformed_{}'.format(v_cube.name())) | ||
|
|
||
| # Calculate mask based on preservation of magnitude. | ||
| mask = _transform_distance_vectors_tolerance_mask(src_crs, x, y, | ||
| target_crs) | ||
| apply_mask = mask.any() | ||
| if apply_mask: | ||
| # Make masked arrays to accept masking. | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think you can leave this to the end, and apply the mask to the entire result with broadcasting. However, the transformed results can have NaNs in, and we should also convert those to masked.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You coudl do it at the end with broadcasting, but it's tricky to do it in an additive way i.e. not unset any existing mask. I'd love it if you could you show me how to do this.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I looked at this and its messy as simple broadcasting provided by numpy doesn't cut it. You need to take into account the dimension mapping. |
||
| ut_cube.data = ma.asanyarray(ut_cube.data) | ||
| vt_cube.data = ma.asanyarray(vt_cube.data) | ||
|
|
||
| # Project vectors with u, v components one horiz slice at a time and | ||
| # insert into the resulting cubes. | ||
| shape = list(u_cube.shape) | ||
|
|
@@ -995,13 +1050,26 @@ def rotate_winds(u_cube, v_cube, target_cs): | |
| u = u_cube.data[index] | ||
| v = v_cube.data[index] | ||
| ut, vt = _transform_distance_vectors(src_crs, x, y, u, v, target_crs) | ||
| if apply_mask: | ||
| ut = ma.asanyarray(ut) | ||
| ut[mask] = ma.masked | ||
| vt = ma.asanyarray(vt) | ||
| vt[mask] = ma.masked | ||
| ut_cube.data[index] = ut | ||
| vt_cube.data[index] = vt | ||
|
|
||
| # Calculate new coords of locations in target coordinate system. | ||
| xyz_tran = target_crs.transform_points(src_crs, x, y) | ||
| xt = xyz_tran[..., 0].reshape(x.shape) | ||
| yt = xyz_tran[..., 1].reshape(y.shape) | ||
|
|
||
| # Transpose xt, yt 2d arrays to match the dim order | ||
| # of the original x an y arrays - i.e. undo the earlier | ||
| # transpose (if applied). | ||
| if dims[0] > dims[1]: | ||
| xt = xt.transpose() | ||
| yt = yt.transpose() | ||
|
|
||
| xt_coord = iris.coords.AuxCoord(xt, | ||
| standard_name='projection_x_coordinate', | ||
| coord_system=target_cs) | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Only a tiny point, but I think this would all look simpler with some renames, so that these 2 lines would read (something like):