Repository: pydata/xarray

Issue:
# support 1-dimensional coordinates in `NDPointIndex`

### What is your issue?

`NDPointIndex` supports indexing along multiple indexed coordinates at the same time, but only for coordinates that have 2 or more dimensions (usually curvilinear grids, like the one used by the `ROMS_example` tutorial dataset).

This excludes indexing scattered points or trajectories.

For example, what I'd like to index is something like this:
```python
import xarray as xr
import numpy as np

ds = xr.Dataset(
    coords={
        "x": ("points", np.linspace(0, 10, 20)),
        "y": ("points", np.linspace(15, 8, 20)),
    }
)
```

cc @dcherian, @benbovy, @quentinf00

Implementation patch:
diff --git a/xarray/indexes/nd_point_index.py b/xarray/indexes/nd_point_index.py
index 1469d59e4fa..e00e6ad608a 100644
--- a/xarray/indexes/nd_point_index.py
+++ b/xarray/indexes/nd_point_index.py
@@ -244,7 +244,7 @@ def __init__(
         assert isinstance(tree_obj, TreeAdapter)
         self._tree_obj = tree_obj
 
-        assert len(coord_names) == len(dims) == len(shape)
+        assert len(dims) == len(shape)
         self._coord_names = coord_names
         self._dims = dims
         self._shape = shape
@@ -264,12 +264,6 @@ def from_variables(
 
         var0 = next(iter(variables.values()))
 
-        if len(variables) != len(var0.dims):
-            raise ValueError(
-                f"the number of variables {len(variables)} doesn't match "
-                f"the number of dimensions {len(var0.dims)}"
-            )
-
         opts = dict(options)
 
         tree_adapter_cls: type[T_TreeAdapter] = opts.pop("tree_adapter_cls", None)


Test patch:
diff --git a/xarray/tests/test_nd_point_index.py b/xarray/tests/test_nd_point_index.py
index 7c2cb8302c6..823d1f21a95 100644
--- a/xarray/tests/test_nd_point_index.py
+++ b/xarray/tests/test_nd_point_index.py
@@ -39,9 +39,6 @@ def test_tree_index_init_errors() -> None:
     xx, yy = np.meshgrid([1.0, 2.0], [3.0, 4.0])
     ds = xr.Dataset(coords={"xx": (("y", "x"), xx), "yy": (("y", "x"), yy)})
 
-    with pytest.raises(ValueError, match="number of variables"):
-        ds.set_xindex("xx", NDPointIndex)
-
     ds2 = ds.assign_coords(yy=(("u", "v"), [[3.0, 3.0], [4.0, 4.0]]))
 
     with pytest.raises(ValueError, match="same dimensions"):
@@ -193,3 +190,34 @@ def test_tree_index_rename() -> None:
         method="nearest",
     )
     assert_identical(actual, expected)
+
+
+@requires_scipy
+def test_tree_index_1d_coords() -> None:
+    ds = xr.Dataset(
+        {"temp": ("points", np.arange(20, dtype=float))},
+        coords={
+            "x": ("points", np.linspace(0, 10, 20)),
+            "y": ("points", np.linspace(15, 8, 20)),
+        },
+    )
+    ds_indexed = ds.set_xindex(("x", "y"), NDPointIndex)
+
+    assert isinstance(ds_indexed.xindexes["x"], NDPointIndex)
+
+    actual = ds_indexed.sel(x=5.0, y=11.5, method="nearest")
+    assert actual.temp.values == 10.0
+
+    actual = ds_indexed.sel(
+        x=xr.Variable("query", [0.0, 5.0, 10.0]),
+        y=xr.Variable("query", [15.0, 11.5, 8.0]),
+        method="nearest",
+    )
+    expected = xr.Dataset(
+        {"temp": ("query", [0.0, 10.0, 19.0])},
+        coords={
+            "x": ("query", [0.0, ds.x.values[10], 10.0]),
+            "y": ("query", [15.0, ds.y.values[10], 8.0]),
+        },
+    )
+    assert_identical(actual, expected)