Repository: corteva/rioxarray

Issue:
"incorrect" bounds (clip_box, pad_box)
#### Code Sample

```python
ds = xr.Dataset(None, {"x": range(5), "y": range(5)})
ds.rio.bounds()
```

This returns `(-0.5, 4.5, 4.5, -0.5)`, where the values represent `xmin, ymin, xmax, ymax` I believe. As you can see, `ymin > ymax`. I can solve this by assuring that the `y` coordinates are descending instead of ascending:

```python
ds = xr.Dataset(None, {"x": range(5), "y": range(5)})
ds = ds.sortby(ds.y, ascending=False)
ds.rio.bounds()
```
Gives `(-0.5, -0.5, 4.5, 4.5)`.

#### Problem description
I'm trying to use `rio.clip_box` and then `rio.pad_box` on a dataset, but I'm running into errors which are solved when making sure that `ymin` is smaller than `ymax`.

Shouldn't `rio.bounds` check what is the actual minimum value in `ds.y` (e.g. doing somethings like `ds.y.min()`), instead of doing something like `ds.y.isel(y = 0)`?

#### Environment Information
```
rioxarray (0.11.1) deps:
  rasterio: 1.2.10
    xarray: 2022.3.0
      GDAL: 3.3.2
      GEOS: None
      PROJ: None
 PROJ DATA: None
 GDAL DATA: None

Other python deps:
     scipy: 1.8.0
    pyproj: 3.3.0

System:
    python: 3.8.12 | packaged by conda-forge | (default, Jan 30 2022, 23:33:09)  [Clang 11.1.0 ]
executable: /Users/hmcoerver/miniconda3/envs/pywapor/bin/python
   machine: macOS-10.16-x86_64-i386-64bit
```

#### Conda environment information (if you installed with conda):

<br/>
Environment (<code>conda list</code>):
<details>

```
gdal                      3.4.2            py38h0b97839_0    conda-forge
libgdal                   3.4.2                h52a0caa_0    conda-forge
rasterio                  1.2.10                   pypi_0    pypi
rioxarray                 0.11.1                   pypi_0    pypi
xarray                    2022.3.0                 pypi_0    pypi

```
</details>

<br/>
Details about  <code>conda</code> and system ( <code>conda info</code> ):
<details>

```
     active environment : pywapor
    active env location : /Users/hmcoerver/miniconda3/envs/pywapor
            shell level : 2
       user config file : /Users/hmcoerver/.condarc
 populated config files : 
          conda version : 4.11.0
    conda-build version : 3.21.4
         python version : 3.8.12.final.0
       virtual packages : __osx=10.16=0
                          __unix=0=0
                          __archspec=1=x86_64
       base environment : /Users/hmcoerver/miniconda3  (writable)
      conda av data dir : /Users/hmcoerver/miniconda3/etc/conda
  conda av metadata url : None
           channel URLs : https://repo.anaconda.com/pkgs/main/osx-64
                          https://repo.anaconda.com/pkgs/main/noarch
                          https://repo.anaconda.com/pkgs/r/osx-64
                          https://repo.anaconda.com/pkgs/r/noarch
          package cache : /Users/hmcoerver/miniconda3/pkgs
                          /Users/hmcoerver/.conda/pkgs
       envs directories : /Users/hmcoerver/miniconda3/envs
                          /Users/hmcoerver/.conda/envs
               platform : osx-64
             user-agent : conda/4.11.0 requests/2.27.1 CPython/3.8.12 Darwin/20.6.0 OSX/10.16
                UID:GID : 501:20
             netrc file : None
           offline mode : False

```
</details>


Discussion:
codecov[bot]: # [Codecov](https://codecov.io/gh/corteva/rioxarray/pull/560?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=corteva) Report
> Merging [#560](https://codecov.io/gh/corteva/rioxarray/pull/560?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=corteva) (28dc724) into [master](https://codecov.io/gh/corteva/rioxarray/commit/f9ce51ed06359a1fe81ac9c3fde3906551b84e25?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=corteva) (f9ce51e) will **increase** coverage by `0.01%`.
> The diff coverage is `89.47%`.

```diff
@@            Coverage Diff             @@
##           master     #560      +/-   ##
==========================================
+ Coverage   96.22%   96.24%   +0.01%     
==========================================
  Files          13       13              
  Lines        1591     1596       +5     
==========================================
+ Hits         1531     1536       +5     
  Misses         60       60              
```


| [Impacted Files](https://codecov.io/gh/corteva/rioxarray/pull/560?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=corteva) | Coverage Δ | |
|---|---|---|
| [rioxarray/rioxarray.py](https://codecov.io/gh/corteva/rioxarray/pull/560/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=corteva#diff-cmlveGFycmF5L3Jpb3hhcnJheS5weQ==) | `96.58% <87.50%> (-0.36%)` | :arrow_down: |
| [rioxarray/raster\_array.py](https://codecov.io/gh/corteva/rioxarray/pull/560/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=corteva#diff-cmlveGFycmF5L3Jhc3Rlcl9hcnJheS5weQ==) | `97.73% <100.00%> (+0.56%)` | :arrow_up: |

Help us with your feedback. Take ten seconds to tell us [how you rate us](https://about.codecov.io/nps?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=corteva). Have a feature suggestion? [Share it here.](https://app.codecov.io/gh/feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=corteva)


Patch:
diff --git a/docs/history.rst b/docs/history.rst
index 7563f87e..78f583d7 100644
--- a/docs/history.rst
+++ b/docs/history.rst
@@ -5,6 +5,7 @@ Latest
 ------
 - BUG: Fix reading file handle with dask (issue #550)
 - BUG: Fix reading cint16 files with dask (issue #542)
+- BUG: Ensure `rio.bounds` ordered correctly (issue #545)
 
 0.11.1
 ------
diff --git a/rioxarray/raster_array.py b/rioxarray/raster_array.py
index 3bf16853..3b3b8720 100644
--- a/rioxarray/raster_array.py
+++ b/rioxarray/raster_array.py
@@ -50,7 +50,12 @@ from rioxarray.raster_writer import (
     RasterioWriter,
     _ensure_nodata_dtype,
 )
-from rioxarray.rioxarray import XRasterBase, _get_data_var_message, _make_coords
+from rioxarray.rioxarray import (
+    XRasterBase,
+    _get_data_var_message,
+    _make_coords,
+    _order_bounds,
+)
 
 # DTYPE TO NODATA MAP
 # Based on: https://github.com/OSGeo/gdal/blob/
@@ -744,21 +749,17 @@ class RasterArray(XRasterBase):
                 f"{_get_data_var_message(self._obj)}"
             )
 
+        resolution_x, resolution_y = self.resolution()
         # make sure that if the coordinates are
         # in reverse order that it still works
-        resolution_x, resolution_y = self.resolution()
-        if resolution_y < 0:
-            top = maxy
-            bottom = miny
-        else:
-            top = miny
-            bottom = maxy
-        if resolution_x < 0:
-            left = maxx
-            right = minx
-        else:
-            left = minx
-            right = maxx
+        left, bottom, right, top = _order_bounds(
+            minx=minx,
+            miny=miny,
+            maxx=maxx,
+            maxy=maxy,
+            resolution_x=resolution_x,
+            resolution_y=resolution_y,
+        )
 
         # pull the data out
         window_error = None
diff --git a/rioxarray/rioxarray.py b/rioxarray/rioxarray.py
index 9819e75e..d22a7b50 100644
--- a/rioxarray/rioxarray.py
+++ b/rioxarray/rioxarray.py
@@ -215,6 +215,33 @@ def _has_spatial_dims(
     return True
 
 
+def _order_bounds(
+    minx: float,
+    miny: float,
+    maxx: float,
+    maxy: float,
+    resolution_x: float,
+    resolution_y: float,
+) -> Tuple[float, float, float, float]:
+    """
+    Make sure that the bounds are in the correct order
+    """
+    if resolution_y < 0:
+        top = maxy
+        bottom = miny
+    else:
+        top = miny
+        bottom = maxy
+    if resolution_x < 0:
+        left = maxx
+        right = minx
+    else:
+        left = minx
+        right = maxx
+
+    return left, bottom, right, top
+
+
 class XRasterBase:
     """This is the base class for the GIS extensions for xarray"""
 
@@ -626,7 +653,7 @@ class XRasterBase:
             return transform
 
         try:
-            src_left, _, _, src_top = self.bounds(recalc=recalc)
+            src_left, _, _, src_top = self._unordered_bounds(recalc=recalc)
             src_resolution_x, src_resolution_y = self.resolution(recalc=recalc)
         except (DimensionMissingCoordinateError, DimensionError):
             return Affine.identity() if transform is None else transform
@@ -981,8 +1008,12 @@ class XRasterBase:
         resolution_y = (bottom - top) / (self.height - 1)
         return resolution_x, resolution_y
 
-    def bounds(self, recalc: bool = False) -> Tuple[float, float, float, float]:
+    def _unordered_bounds(
+        self, recalc: bool = False
+    ) -> Tuple[float, float, float, float]:
         """
+        Unordered bounds.
+
         Parameters
         ----------
         recalc: bool, optional
@@ -1014,6 +1045,24 @@ class XRasterBase:
 
         return left, bottom, right, top
 
+    def bounds(self, recalc: bool = False) -> Tuple[float, float, float, float]:
+        """
+        Parameters
+        ----------
+        recalc: bool, optional
+            Will force the bounds to be recalculated instead of using the
+            transform attribute.
+
+        Returns
+        -------
+        left, bottom, right, top: float
+            Outermost coordinates of the `xarray.DataArray` | `xarray.Dataset`.
+        """
+        return _order_bounds(
+            *self._unordered_bounds(recalc=recalc),
+            *self.resolution(recalc=recalc),
+        )
+
     def isel_window(
         self, window: rasterio.windows.Window, pad: bool = False
     ) -> Union[xarray.Dataset, xarray.DataArray]:


Test patch:
diff --git a/test/integration/test_integration_rioxarray.py b/test/integration/test_integration_rioxarray.py
index cbc7c1bf..94aa9d6c 100644
--- a/test/integration/test_integration_rioxarray.py
+++ b/test/integration/test_integration_rioxarray.py
@@ -2901,3 +2901,15 @@ def test_reproject__gcps_file(tmp_path):
             2818720.0,
         )
     )
+
+
+def test_bounds__ordered__dataarray():
+    xds = xarray.DataArray(
+        numpy.zeros((5, 5)), dims=("y", "x"), coords={"x": range(5), "y": range(5)}
+    )
+    assert xds.rio.bounds() == (-0.5, -0.5, 4.5, 4.5)
+
+
+def test_bounds__ordered__dataset():
+    xds = xarray.Dataset(None, coords={"x": range(5), "y": range(5)})
+    assert xds.rio.bounds() == (-0.5, -0.5, 4.5, 4.5)