Skip to content

Commit 7ce67d2

Browse files
committed
dev
1 parent 5820978 commit 7ce67d2

5 files changed

Lines changed: 395 additions & 261 deletions

File tree

cf/data/data.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@ def __init__(
178178
copy=True,
179179
dtype=None,
180180
mask=None,
181-
masked_values=None,
181+
mask_value=None,
182182
to_memory=False,
183183
init_options=None,
184184
_use_array=True,
@@ -267,6 +267,12 @@ def __init__(
267267
268268
.. versionadded:: 3.0.5
269269
270+
mask_value: scalar array_like
271+
Mask *array* where it is equal to *mask_value*, using
272+
numerically tolerant floating point equality.
273+
274+
.. versionadded:: UGRIDVER
275+
270276
{{init source: optional}}
271277
272278
hardmask: `bool`, optional
@@ -486,8 +492,8 @@ def __init__(
486492
self.where(mask, cf_masked, inplace=True)
487493

488494
# Apply masked values
489-
if masked_values is not None:
490-
self.masked_values(masked_values, inplace=True)
495+
if mask_value is not None:
496+
self.masked_values(mask_value, inplace=True)
491497

492498
@property
493499
def dask_compressed_array(self):

cf/field.py

Lines changed: 24 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3198,9 +3198,9 @@ def weights(
31983198
3. Area calculated from X and Y
31993199
dimension coordinate constructs
32003200
with bounds
3201-
4. Area calculated from 1-d face or
3202-
polygon auxiliary coordinate
3203-
constructs.
3201+
4. Area calculated from 1-d auxiliary
3202+
coordinate constructs for geomtries
3203+
or a UGRID mesh topology.
32043204
5. Length calculated from 1-d edge or
32053205
line auxiliary coordinate
32063206
constructs.
@@ -3239,13 +3239,22 @@ def weights(
32393239
============ ==========================================
32403240
*weights* Description
32413241
============ ==========================================
3242-
``'area'`` Cell area weights from the field
3243-
construct's area cell measure construct
3244-
or, if one doesn't exist, from (grid)
3245-
latitude and (grid) longitude dimension
3246-
coordinate constructs. Set the *methods*
3247-
parameter to find out how the weights were
3248-
actually created.
3242+
``'area'`` Cell area weights. The weights
3243+
components are created for axes of the
3244+
field by the following methods, in
3245+
order of preference,
3246+
3247+
1. Area cell measures.
3248+
2. X and Y dimension coordinate
3249+
constructs with bounds.
3250+
3. X and Y 1-d auxiliary coordinate
3251+
constructs for polygon cells
3252+
defined by geometries or a UGRID
3253+
mesh topology.
3254+
3255+
Set the *methods* parameter to find
3256+
out how the weights were actually
3257+
created.
32493258

32503259
``'volume'`` Cell volume weights from the field
32513260
construct's volume cell measure construct.
@@ -3627,10 +3636,10 @@ def weights(
36273636
else:
36283637
axes.append(weights)
36293638
else:
3630-
# In rare edge cases, e.g. if a user sets:
3631-
# weights=f[0].cell_area
3632-
# when they mean weights=f[0].cell_area(), we reach this
3633-
# code but weights is not iterable. So check it is first:
3639+
# In rare edge cases (e.g. if a user sets
3640+
# `weights=f[0].cell_area` when they really meant
3641+
# `weights=f[0].cell_area()`) we reach this code but
3642+
# find that weights is not iterable. So check it is.x
36343643
try:
36353644
weights = iter(weights)
36363645
except TypeError:
@@ -6549,6 +6558,7 @@ def collapse(
65496558
a = self.domain_axis(x, key=True, default=None)
65506559
if a is None:
65516560
raise ValueError(msg.format(x))
6561+
65526562
axes2.append(a)
65536563

65546564
all_axes.append(axes2)
@@ -6568,8 +6578,6 @@ def collapse(
65686578
#
65696579
# ------------------------------------------------------------
65706580
domain_axes = f.domain_axes(todict=False, cached=domain_axes)
6571-
# auxiliary_coordinates = f.auxiliary_coordinates(view=True)
6572-
# dimension_coordinates = f.dimension_coordinates(view=True)
65736581

65746582
for method, axes, within, over, axes_in in zip(
65756583
all_methods, all_axes, all_within, all_over, input_axes
@@ -6579,8 +6587,6 @@ def collapse(
65796587
raise ValueError(f"Unknown collapse method: {method!r}")
65806588

65816589
method = method2
6582-
6583-
# collapse_axes_all_sizes = domain_axes.filter_by_key(*axes)
65846590
collapse_axes_all_sizes = f.domain_axes(
65856591
filter_by_key=axes, todict=False
65866592
)

cf/test/test_Data.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4714,6 +4714,24 @@ def test_Data_todict(self):
47144714
self.assertIn((key, 0), x)
47154715
self.assertIn((key, 1), x)
47164716

4717+
def test_Data_masked_values(self):
4718+
"""Test Data.masked_values."""
4719+
array = np.array([[1, 1.1, 2, 1.1, 3]])
4720+
d = cf.Data(array)
4721+
e = d.masked_values(1.1)
4722+
ea = e.array
4723+
a = np.ma.masked_values(array, 1.1, rtol=cf.rtol(), atol=cf.atol())
4724+
self.assertTrue(np.isclose(ea, a).all())
4725+
self.assertTrue((ea.mask == a.mask).all())
4726+
self.assertIsNone(d.masked_values(1.1, inplace=True))
4727+
self.assertTrue(d.equals(e))
4728+
4729+
array = np.array([[1, 1.1, 2, 1.1, 3]])
4730+
d = cf.Data(array, mask_value=1.1)
4731+
da = e.array
4732+
self.assertTrue(np.isclose(da, a).all())
4733+
self.assertTrue((da.mask == a.mask).all())
4734+
47174735

47184736
if __name__ == "__main__":
47194737
print("Run date:", datetime.datetime.now())

cf/test/test_weights.py

Lines changed: 170 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,185 @@
1+
import datetime
2+
import unittest
3+
14
import numpy as np
25

36
import cf
47

5-
f = cf.example_field(6)
6-
f.del_construct("auxiliarycoordinate3")
7-
f.del_construct("grid_mapping_name:latitude_longitude")
8-
g = f.copy()
9-
g.dump()
108

9+
class WeightsTest(unittest.TestCase):
10+
def test_weights_polygon_area_geometry(self):
11+
f = cf.example_field(6)
12+
f.del_construct("auxiliarycoordinate3")
13+
f.del_construct("grid_mapping_name:latitude_longitude")
14+
15+
# Surface area of unit sphere
16+
sphere_area = 4 * np.pi
17+
correct_weights = np.array([sphere_area / 8, sphere_area / 4])
18+
19+
# Spherical polygon geometry weights with duplicated first/last
20+
# node
21+
lon = cf.AuxiliaryCoordinate()
22+
lon.standard_name = "longitude"
23+
bounds = cf.Data(
24+
[[315, 45, 45, 315, 999, 999, 999], [90, 90, 0, 45, 45, 135, 90]],
25+
"degrees_east",
26+
mask_value=999,
27+
).reshape(2, 1, 7)
28+
lon.set_bounds(cf.Bounds(data=bounds))
29+
lon.set_geometry("polygon")
30+
31+
lat = cf.AuxiliaryCoordinate()
32+
lat.standard_name = "latitude"
33+
bounds = cf.Data(
34+
[[0, 0, 90, 0, 999, 999, 999], [0, 90, 0, 0, -90, 0, 0]],
35+
"degrees_north",
36+
mask_value=999,
37+
).reshape(2, 1, 7)
38+
lat.set_bounds(cf.Bounds(data=bounds))
39+
lat.set_geometry("polygon")
40+
41+
f.del_construct("longitude")
42+
f.del_construct("latitude")
43+
44+
f.set_construct(lon, axes="domainaxis0", copy=False)
45+
f.set_construct(lat, axes="domainaxis0", copy=False)
46+
47+
w = f.weights("X", great_circle=True)
48+
self.assertTrue((w.array == correct_weights).all())
49+
50+
r = 2
51+
w = f.weights("area", great_circle=True, measure=True, radius=r)
52+
self.assertTrue((w.array == (r**2) * correct_weights).all())
53+
54+
# Spherical polygon geometry weights without duplicated
55+
# first/last node
56+
bounds = cf.Data(
57+
[[315, 45, 45, 999, 999, 999], [90, 90, 0, 45, 45, 135]],
58+
"degrees_east",
59+
mask_value=999,
60+
).reshape(2, 1, 6)
61+
lon.set_bounds(cf.Bounds(data=bounds))
62+
63+
bounds = cf.Data(
64+
[[0, 0, 90, 999, 999, 999], [0, 90, 0, 0, -90, 0]],
65+
"degrees_north",
66+
mask_value=999,
67+
).reshape(2, 1, 6)
68+
lat.set_bounds(cf.Bounds(data=bounds))
69+
70+
w = f.weights("X", great_circle=True)
71+
self.assertTrue((w.array == correct_weights).all())
72+
73+
r = 2
74+
w = f.weights("area", great_circle=True, measure=True, radius=r)
75+
self.assertTrue((w.array == (r**2) * correct_weights).all())
76+
77+
# Plane polygon geometry weights with no duplicated first/last
78+
# nodes, and an interior ring
79+
lon.override_units("m", inplace=True)
80+
lon.standard_name = "projection_x_coordinate"
81+
bounds = cf.Data(
82+
[
83+
[
84+
[2, 2, 0, 0, 999, 999, 999, 999], # anticlockwise
85+
[0.5, 1.5, 1.5, 0.5, 999, 999, 999, 999],
86+
], # clockwise
87+
[
88+
[2, 2, 0, 0, 1, 1, 3, 3],
89+
[999, 999, 999, 999, 999, 999, 999, 999],
90+
],
91+
],
92+
"m",
93+
mask_value=999,
94+
).reshape(2, 2, 8)
95+
lon.set_bounds(cf.Bounds(data=bounds))
96+
lon.set_interior_ring(cf.InteriorRing(data=[[0, 1], [0, 0]]))
97+
98+
lat.override_units("m", inplace=True)
99+
lat.standard_name = "projection_y_coordinate"
100+
bounds = cf.Data(
101+
[
102+
[
103+
[-1, 1, 1, -1, 999, 999, 999, 999], # anticlockwise
104+
[0.5, 0.5, -0.5, -0.5, 999, 999, 999, 999],
105+
], # clockwise
106+
[
107+
[-1, 1, 1, -1, -1, -3, -3, -1],
108+
[999, 999, 999, 999, 999, 999, 999, 999],
109+
],
110+
],
111+
"m",
112+
mask_value=999,
113+
).reshape(2, 2, 8)
114+
lat.set_bounds(cf.Bounds(data=bounds))
115+
lat.set_interior_ring(cf.InteriorRing(data=[[0, 1], [0, 0]]))
116+
117+
correct_weights = np.array([3, 8])
118+
w = f.weights("area")
119+
self.assertTrue((w.array == correct_weights).all())
120+
121+
def test_weights_polygon_area_ugrid(self):
122+
f = cf.example_field(8)
123+
f = f[..., [0, 2]]
11124

12-
lon = cf.AuxiliaryCoordinate()
13-
lon.standard_name = "longitude"
14-
bounds = cf.Data(
15-
[[315, 45, 45, 315, 999, 999, 999], [90, 90, 0, 45, 45, 135, 90]],
16-
"degrees_east",
17-
masked_values=999,
18-
).reshape(2, 1, 7)
19-
lon.set_bounds(cf.Bounds(data=bounds))
20-
lon.set_geometry("polygon")
125+
# Surface area of unit sphere
126+
sphere_area = 4 * np.pi
127+
correct_weights = np.array([sphere_area / 8, sphere_area / 4])
21128

22-
lat = cf.AuxiliaryCoordinate()
23-
lat.standard_name = "latitude"
24-
bounds = cf.Data(
25-
[[0, 0, 90, 0, 999, 999, 999], [0, 90, 0, 0, -90, 0, 0]],
26-
"degrees_north",
27-
masked_values=999,
28-
).reshape(2, 1, 7)
29-
lat.set_bounds(cf.Bounds(data=bounds))
30-
lat.set_geometry("polygon")
31-
lon.dump()
32-
f.del_construct("longitude")
33-
f.del_construct("latitude")
129+
# Spherical polygon weights
130+
lon = f.auxiliary_coordinate("X")
131+
lon.del_data()
132+
bounds = cf.Data(
133+
[[315, 45, 45, 999, 999, 999], [90, 90, 0, 45, 45, 135]],
134+
"degrees_east",
135+
mask_value=999,
136+
).reshape(2, 6)
137+
lon.set_bounds(cf.Bounds(data=bounds))
34138

35-
# f.dump()
36-
f.set_construct(lon, axes="domainaxis0")
37-
f.set_construct(lat, axes="domainaxis0")
38-
print(f.constructs)
139+
lat = f.auxiliary_coordinate("Y")
140+
bounds = cf.Data(
141+
[[0, 0, 90, 999, 999, 999], [0, 90, 0, 0, -90, 0]],
142+
"degrees_north",
143+
mask_value=999,
144+
).reshape(2, 6)
145+
lat.set_bounds(cf.Bounds(data=bounds))
39146

40-
f.dump()
147+
w = f.weights("X", great_circle=True)
148+
self.assertTrue((w.array == correct_weights).all())
41149

42-
sphere_area = 4 * np.pi
150+
r = 2
151+
w = f.weights("area", great_circle=True, measure=True, radius=r)
152+
self.assertTrue((w.array == (r**2) * correct_weights).all())
43153

154+
# Plane polygon weights
155+
lon.override_units("m", inplace=True)
156+
lon.standard_name = "projection_x_coordinate"
157+
bounds = cf.Data(
158+
[[2, 2, 0, 0, 999, 999, 999, 999], [2, 2, 0, 0, 1, 1, 3, 3]],
159+
"m",
160+
mask_value=999,
161+
).reshape(2, 8)
162+
lon.set_bounds(cf.Bounds(data=bounds))
44163

45-
w = f.weights("X", great_circle=True)
46-
print(repr(w), w.array, np.array([sphere_area / 8, sphere_area / 4]))
164+
lat.override_units("m", inplace=True)
165+
lat.standard_name = "projection_y_coordinate"
166+
bounds = cf.Data(
167+
[
168+
[-1, 1, 1, -1, 999, 999, 999, 999],
169+
[-1, 1, 1, -1, -1, -3, -3, -1],
170+
],
171+
"m",
172+
mask_value=999,
173+
).reshape(2, 8)
174+
lat.set_bounds(cf.Bounds(data=bounds))
47175

48-
print(w.array == np.array([sphere_area / 8, sphere_area / 4]))
176+
correct_weights = np.array([4, 8])
177+
w = f.weights("area")
178+
self.assertTrue((w.array == correct_weights).all())
49179

50-
w = f.weights("X", great_circle=True, measure=True, radius=2)
51-
print(repr(w), w.array, 4 * np.array([sphere_area / 8, sphere_area / 4]))
52180

53-
print(w.array - 4 * np.array([sphere_area / 8, sphere_area / 4]))
181+
if __name__ == "__main__":
182+
print("Run date:", datetime.datetime.now())
183+
cf.environment()
184+
print("")
185+
unittest.main(verbosity=2)

0 commit comments

Comments
 (0)