diff --git a/tests/test_accessor.py b/tests/test_accessor.py new file mode 100644 index 0000000..c938caf --- /dev/null +++ b/tests/test_accessor.py @@ -0,0 +1,41 @@ +import numpy as np +import pytest +import xarray as xr + +import xarray_subset_grid.accessor # noqa: F401 -- register accessor + + +def test_accessor_warns_when_no_grid_recognized(): + ds = xr.Dataset() + with pytest.warns(UserWarning, match="no grid type"): + accessor = ds.xsg + assert accessor.grid is None + + +def test_subset_polygon_and_bbox_return_none_without_grid(): + ds = xr.Dataset() + poly = np.array( + [ + [-72.0, 41.0], + [-70.0, 41.0], + [-71.0, 39.0], + [-72.0, 41.0], + ] + ) + with pytest.warns(UserWarning, match="no grid type"): + assert ds.xsg.subset_polygon(poly) is None + assert ds.xsg.subset_bbox((-72, 39, -70, 41)) is None + + +def test_subset_vars_passthrough_without_grid(): + ds = xr.Dataset({"a": (("x",), [1, 2, 3])}) + with pytest.warns(UserWarning, match="no grid type"): + out = ds.xsg.subset_vars(["a"]) + # Without a recognized grid, subset_vars returns the dataset unchanged. + assert "a" in out.data_vars + + +def test_has_vertical_levels_false_without_grid(): + ds = xr.Dataset() + with pytest.warns(UserWarning, match="no grid type"): + assert ds.xsg.has_vertical_levels is False diff --git a/tests/test_grids/test_sgrid.py b/tests/test_grids/test_sgrid.py index dce7379..5095699 100644 --- a/tests/test_grids/test_sgrid.py +++ b/tests/test_grids/test_sgrid.py @@ -8,6 +8,7 @@ from xarray_subset_grid.grids.sgrid import _get_location_info_from_topology # open dataset as zarr object using fsspec reference file system and xarray +zarr__version__ = 0 try: import fsspec import zarr diff --git a/tests/test_utils.py b/tests/test_utils.py index 6b64a2c..6eb4a8c 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -1,9 +1,17 @@ import os +from datetime import datetime +import cftime import numpy as np import pytest +import xarray as xr +from tests.conftest import EXAMPLE_DATA +from xarray_subset_grid import utils as xsg_utils from xarray_subset_grid.utils import ( + asdatetime, + compute_2d_subset_mask, + format_bytes, normalize_bbox_x_coords, normalize_polygon_x_coords, ray_tracing_numpy, @@ -125,3 +133,72 @@ def test_ray_tracing_numpy(): result = ray_tracing_numpy(points[:, 0], points[:, 1], poly) assert np.array_equal(result, [False, True, False]) + + +@pytest.mark.parametrize( + "num, unit", + [ + (512, "bytes"), + (2048, "KB"), + (3 * 1024**2, "MB"), + ], +) +def test_format_bytes(num, unit): + assert unit in format_bytes(num) + + +def test_asdatetime_none(): + assert asdatetime(None) is None + + +def test_asdatetime_datetime_passthrough(): + dt = datetime(2020, 6, 15, 12, 30, 0) + assert asdatetime(dt) is dt + + +def test_asdatetime_cftime_passthrough(): + dt = cftime.datetime(2020, 6, 15, 12) + assert asdatetime(dt) is dt + + +def test_asdatetime_parse_string(): + dt = asdatetime("2020-06-15T12:30:00") + assert dt.year == 2020 and dt.month == 6 and dt.day == 15 + + +def test_compute_2d_subset_mask_all_inside(): + ny, nx = 5, 5 + lat = np.linspace(40.0, 44.0, ny) + lon = np.linspace(-74.0, -70.0, nx) + lat2d, lon2d = np.meshgrid(lat, lon, indexing="ij") + lat_da = xr.DataArray(lat2d, dims=("y", "x")) + lon_da = xr.DataArray(lon2d, dims=("y", "x")) + poly = np.array([(-75.0, 39.0), (-69.0, 39.0), (-69.0, 45.0), (-75.0, 45.0)]) + mask = compute_2d_subset_mask(lat_da, lon_da, poly) + assert mask.dims == ("y", "x") + assert bool(mask.all()) + + +def test_compute_2d_subset_mask_partial(): + ny, nx = 7, 7 + lat = np.linspace(40.0, 46.0, ny) + lon = np.linspace(-74.0, -68.0, nx) + lat2d, lon2d = np.meshgrid(lat, lon, indexing="ij") + lat_da = xr.DataArray(lat2d, dims=("y", "x")) + lon_da = xr.DataArray(lon2d, dims=("y", "x")) + # Small polygon over the south-west corner only + poly = np.array([(-74.5, 40.0), (-73.0, 40.0), (-73.0, 41.0), (-74.5, 41.0)]) + mask = compute_2d_subset_mask(lat_da, lon_da, poly) + assert mask.dims == ("y", "x") + assert bool(mask.any()) + assert not bool(mask.all()) + + +def test_assign_ugrid_topology_utils_deprecation_wrapper(): + nc = EXAMPLE_DATA / "SFBOFS_subset1.nc" + if not nc.is_file(): + pytest.skip("example NetCDF not present") + ds = xr.open_dataset(nc) + with pytest.warns(DeprecationWarning, match="assign_ugrid_topology"): + ds2 = xsg_utils.assign_ugrid_topology(ds, face_node_connectivity="nv") + assert "mesh" in ds2.variables diff --git a/xarray_subset_grid/utils.py b/xarray_subset_grid/utils.py index e004525..da35800 100644 --- a/xarray_subset_grid/utils.py +++ b/xarray_subset_grid/utils.py @@ -19,12 +19,14 @@ def normalize_polygon_x_coords(x, poly): If the x coords are between 0 and 180 (i.e. both will work), the polygon is not changed. - NOTE: polygon is normalized in place! + NOTE: ``poly`` is normalized in place when it is already an ndarray; + a copy is made when ``poly`` is a sequence. Args: x (np.array): x-coordinates of the vertices poly (np.array): polygon vertices """ + poly = np.asarray(poly) x_min, x_max = x.min(), x.max() poly_x = poly[:, 0] @@ -97,10 +99,11 @@ def ray_tracing_numpy(x, y, poly): # this placeholder for backwards compatibility for a brief period def assign_ugrid_topology(*args, **kwargs): warnings.warn( - DeprecationWarning, - "The function `assign_grid_topology` has been moved to the " + "The function `assign_ugrid_topology` has been moved to the " "`grids.ugrid` module. It will not be able to be called from " "the utils `module` in the future.", + DeprecationWarning, + stacklevel=2, ) from .grids.ugrid import assign_ugrid_topology