diff --git a/docs/examples/example_data/tris_and_bounds.nc b/docs/examples/example_data/tris_and_bounds.nc new file mode 100644 index 0000000..71b4ed2 Binary files /dev/null and b/docs/examples/example_data/tris_and_bounds.nc differ diff --git a/pixi.lock b/pixi.lock index dc5b884..a98ab8f 100644 --- a/pixi.lock +++ b/pixi.lock @@ -27218,12 +27218,12 @@ packages: timestamp: 1726135055732 - kind: pypi name: xarray-subset-grid - version: 0.1.dev69+g36108f3.d20241009 + version: 0.1.dev74+gca5b65a.d20241009 path: . - sha256: 0343cd5d65d9be7c988056539ba591ba74391e1bbd6351de715000eacffa3d3f + sha256: d0a1f25417ac26c66bc37bd88b86ac3a868dcde16431260e56277c4913ded2bc requires_dist: - numpy - - xarray>=2023.10.0 + - xarray>=2024.6 - cf-xarray - cftime - dask[complete] diff --git a/pyproject.toml b/pyproject.toml index 685fc0a..78bec42 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [build-system] -requires = ["setuptools>=61", "wheel", "setuptools_scm[toml]>=6.2"] +requires = ["setuptools>=72", "wheel", "setuptools_scm[toml]>=8.0"] build-backend = "setuptools.build_meta" [project] @@ -8,7 +8,7 @@ authors = [ { name = "Matthew Iannucci", email = "matt.iannucci@tetratech.com" }, { name = "Christopher H. Barker", email = "chris.barker@noaa.gov" }, ] -description = "Subset Xarray datasets in space" +description = "Subset Xarray datasets in time and space" readme = "README.md" requires-python = ">=3.10" keywords = ["xarray"] @@ -31,7 +31,7 @@ dynamic = ["version"] dependencies = [ "numpy", - "xarray>=2023.10.0", + "xarray>=2024.6", "cf_xarray", "cftime", "dask[complete]", @@ -99,7 +99,7 @@ default = { solve-group = "default" } dev = { features = ["dev"], solve-group = "default" } examples = { features = ["examples"], solve-group = "default" } all = { features = ["dev", "examples"], solve-group = "default" } -test310 = ["dev", "py310"] # implicit: test310 = ["dev", "py310", "default"] +test310 = ["dev", "py310"] # implicit: test310 = ["dev", "py310", "default"] test311 = ["dev", "py311"] test312 = ["dev", "py312"] @@ -109,8 +109,8 @@ test312 = ["dev", "py312"] [tool.pixi.dependencies] python = ">=3.10" numpy = "*" -xarray = ">=2023.10.0" -pandas = ">=2.2.2" +xarray = ">=2024.6" +pandas = "*" cf_xarray = "*" dask = "*" fsspec = "*" diff --git a/tests/test_grids/test_ugrid.py b/tests/test_grids/test_ugrid.py index f9421fc..dce0a0e 100644 --- a/tests/test_grids/test_ugrid.py +++ b/tests/test_grids/test_ugrid.py @@ -49,6 +49,7 @@ lonc:standard_name = "longitude" ; lonc:long_name = "zonal longitude" ; lonc:units = "degree_east" ; + float latc(nele) ; latc:standard_name = "latitude" ; latc:long_name = "zonal latitude" ; @@ -60,7 +61,7 @@ face_face_connectivity: int nbe(three, nele) ; - nbe:long_name = "elements surrounding anch element" ; + nbe:long_name = "elements surrounding each element" ; The depth coordinates (xarray is not sure what to do with these ...) @@ -87,10 +88,8 @@ time:format = "defined reference date" ; time:time_zone = "UTC" ; - Data on the grid: - float h(node) ; h:standard_name = "sea_floor_depth_below_geoid" ; h:long_name = "Bathymetry" ; @@ -265,7 +264,11 @@ def test_assign_ugrid_topology_min(): assert mesh["face_coordinates"] == "lonc latc" assert mesh["face_face_connectivity"] == "nbe" assert mesh["face_dimension"] == "nele" + assert mesh["start_index"] == 1 + # There should be no empty attributes + for key, val in mesh.items(): + assert val def test_assign_ugrid_topology_dict(): """ @@ -292,6 +295,36 @@ def test_assign_ugrid_topology_dict(): assert mesh["face_face_connectivity"] == "nbe" assert mesh["face_dimension"] == "nele" +def test_assign_ugrid_topology_no_face_coords(): + """ + test for no face coords + + """ + # this example has triangles and bounds -- no face coordinates + ds = xr.open_dataset(EXAMPLE_DATA / "tris_and_bounds.nc") + with pytest.raises(KeyError): + ds["mesh"] + + ds = ugrid.assign_ugrid_topology(ds, face_node_connectivity='tris', + node_coordinates = ('lon lat'), + boundary_node_connectivity = 'bounds' + ) + + # there are others, but these are the ones that really matter. + mesh = ds["mesh"].attrs + assert mesh["cf_role"] == "mesh_topology" + assert mesh["node_coordinates"] == "lon lat" + assert mesh["face_node_connectivity"] == "tris" + assert mesh["face_dimension"] == "tris" + + assert mesh.keys() == {'cf_role', + 'topology_dimension', + 'face_node_connectivity', + 'boundary_node_connectivity', + 'node_coordinates', + 'face_dimension', + 'start_index', + } def test_assign_ugrid_topology_existing_mesh_var(): """ @@ -351,6 +384,7 @@ def test_assign_ugrid_topology_start_index_zero_infer(): assert ds["mesh_boundary_nodes"].attrs["start_index"] == 0 + # NOTE: these tests are probably not complete -- but they are something. # we really should have a complete UGRID example to test with. def test_grid_vars(): diff --git a/xarray_subset_grid/grids/ugrid.py b/xarray_subset_grid/grids/ugrid.py index 81ca36e..0562ff7 100644 --- a/xarray_subset_grid/grids/ugrid.py +++ b/xarray_subset_grid/grids/ugrid.py @@ -413,12 +413,11 @@ def assign_ugrid_topology( raise ValueError( "face_node_connectivity is a required parameter if it is not in the mesh_topology variable" # noqa: E501 ) - if mesh.face_coordinates is None: try: - mesh.face_coordinates = ds[mesh.face_node_connectivity].cf.coordinates + face_coordinates = ds[mesh.face_node_connectivity].cf.coordinates mesh.face_coordinates = " ".join(f"{coord[0]}" - for coord in mesh.face_coordinates.values()) + for coord in face_coordinates.values()) except AttributeError: mesh.face_coordinates = None @@ -487,7 +486,8 @@ def assign_ugrid_topology( # push the non-None mesh attributes back into the variable for key, val in mesh.__dict__.items(): - if val is not None: + # can't use truthiness, as 0 is false + if not ((val is None) or (val == "")): mesh_attrs[key] = val return ds