Repository: fatiando/verde

Issue:
Raise specific error if no spacing nor shape is passed to rolling window
**Description of the desired feature**

The `verde.rolling_window` function has two positional arguments: `coordinates` and `size`, but it also needs one of the `spacing` or `shape` default arguments, which are set to `None` by default.
If by accident, the user doesn't pass either one of them (never happened to me :roll_eyes:), they get a very strange error:

```python
import verde as vd

coordinates = vd.grid_coordinates(region=(-10e3, 10e3, -10e3, 10e3), shape=(30, 30))
vd.rolling_window(coordinates, size=10e3)
```
```
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/santi/git/verde/verde/coordinates.py", line 1027, in rolling_window
    _check_rolling_window_overlap(window_region, size, shape, spacing)
  File "/home/santi/git/verde/verde/coordinates.py", line 1069, in _check_rolling_window_overlap
    if np.any(spacing > size):
TypeError: '>' not supported between instances of 'NoneType' and 'float'
```

Would be nice to check if one of them is not None, and raise an error otherwise, like `vd.block_split` does:

```python
vd.block_split(coordinates)
```
```
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/santi/git/verde/verde/coordinates.py", line 803, in block_split
    region, spacing=spacing, shape=shape, adjust=adjust, pixel_register=True
  File "/home/santi/git/verde/verde/coordinates.py", line 427, in grid_coordinates
    raise ValueError("Either a grid shape or a spacing must be provided.")
ValueError: Either a grid shape or a spacing must be provided.
```

**Are you willing to help implement and maintain this feature?** Yes


Discussion:

Patch:
diff --git a/verde/coordinates.py b/verde/coordinates.py
index b43ebd3..941df0a 100644
--- a/verde/coordinates.py
+++ b/verde/coordinates.py
@@ -1015,11 +1015,21 @@ def rolling_window(
     [20. 20. 20. 20. 20. 20. 20. 20. 20.]
 
     """
+    # Check if shape or spacing were passed
+    if shape is None and spacing is None:
+        raise ValueError("Either a shape or a spacing must be provided.")
     # Select the coordinates after checking to make sure indexing will still
     # work on the ignored coordinates.
     coordinates = check_coordinates(coordinates)[:2]
     if region is None:
         region = get_region(coordinates)
+    # Check if window size is bigger than the minimum dimension of the region
+    region_min_width = min(region[1] - region[0], region[3] - region[2])
+    if region_min_width < size:
+        raise ValueError(
+            "Window size '{}' is larger ".format(size)
+            + "than dimensions of the region '{}'.".format(region)
+        )
     # Calculate the region spanning the centers of the rolling windows
     window_region = [
         dimension + (-1) ** (i % 2) * size / 2 for i, dimension in enumerate(region)


Test patch:
diff --git a/verde/tests/test_coordinates.py b/verde/tests/test_coordinates.py
index ec38c46..0299337 100644
--- a/verde/tests/test_coordinates.py
+++ b/verde/tests/test_coordinates.py
@@ -58,6 +58,40 @@ def test_rolling_window_warnings():
             assert str(userwarnings[-1].message).split()[0] == "Rolling"
 
 
+def test_rolling_window_no_shape_or_spacing():
+    """
+    Check if error is raise if no shape or spacing is passed
+    """
+    coords = grid_coordinates((-5, -1, 6, 10), spacing=1)
+    err_msg = "Either a shape or a spacing must be provided."
+    with pytest.raises(ValueError, match=err_msg):
+        rolling_window(coords, size=2)
+
+
+def test_rolling_window_oversized_window():
+    """
+    Check if error is raised if size larger than region is passed
+    """
+    oversize = 5
+    regions = [
+        (-5, -1, 6, 20),  # window larger than west-east
+        (-20, -1, 6, 10),  # window larger than south-north
+        (-5, -1, 6, 10),  # window larger than both dims
+    ]
+    for region in regions:
+        coords = grid_coordinates(region, spacing=1)
+        # The expected error message with regex
+        # (the long expression intends to capture floats and ints)
+        float_regex = r"[+-]?([0-9]*[.])?[0-9]+"
+        err_msg = (
+            r"Window size '{}' is larger ".format(float_regex)
+            + r"than dimensions of the region "
+            + r"'\({0}, {0}, {0}, {0}\)'.".format(float_regex)
+        )
+        with pytest.raises(ValueError, match=err_msg):
+            rolling_window(coords, size=oversize, spacing=2)
+
+
 def test_spacing_to_shape():
     "Check that correct spacing and region are returned"
     region = (-10, 0, 0, 5)