Repository: corteva/rioxarray

Issue:
Drop python 2 support
It will be removed in rasterio, fiona. Already dropped in pyproj.

Discussion:
snowman2: This is helpful for when the `grid_mapping` attribute is lost but the default `spatial_ref` is still in the `coords`.
snowman2: Unrelated MacOS python 3.6 failure

Patch:
diff --git a/rioxarray/rioxarray.py b/rioxarray/rioxarray.py
index 28597969..ad8eaf99 100644
--- a/rioxarray/rioxarray.py
+++ b/rioxarray/rioxarray.py
@@ -11,7 +11,6 @@ datacube is licensed under the Apache License, Version 2.0:
 
 """
 import copy
-from abc import abstractmethod
 from datetime import datetime
 
 import numpy as np
@@ -222,12 +221,29 @@ class XRasterBase(object):
         self._crs = None
 
     @property
-    @abstractmethod
     def crs(self):
         """:obj:`rasterio.crs.CRS`:
-            The projection of the dataset.
+            Retrieve projection from `xarray.DataArray` or `xarray.Dataset`
         """
-        raise NotImplementedError
+        if self._crs is not None:
+            return None if self._crs is False else self._crs
+
+        try:
+            # look in grid_mapping
+            grid_mapping_coord = self._obj.attrs.get("grid_mapping", DEFAULT_GRID_MAP)
+            try:
+                crs_wkt = self._obj.coords[grid_mapping_coord].attrs["spatial_ref"]
+            except KeyError:
+                crs_wkt = self._obj.coords[grid_mapping_coord].attrs["crs_wkt"]
+            self.set_crs(crs_wkt, inplace=True)
+        except KeyError:
+            try:
+                # look in attrs for 'crs'
+                self.set_crs(self._obj.attrs["crs"], inplace=True)
+            except KeyError:
+                self._crs = False
+                return None
+        return self._crs
 
     def _get_obj(self, inplace):
         """
@@ -325,10 +341,7 @@ class XRasterBase(object):
                     data_obj[var].rio.update_attrs(
                         dict(grid_mapping=grid_mapping_name), inplace=True
                     )
-        else:
-            data_obj.rio.update_attrs(
-                dict(grid_mapping=grid_mapping_name), inplace=True
-            )
+        data_obj.rio.update_attrs(dict(grid_mapping=grid_mapping_name), inplace=True)
         return data_obj
 
     def set_attrs(self, new_attrs, inplace=False):
@@ -508,31 +521,6 @@ class RasterArray(XRasterBase):
         data_obj.rio.set_nodata(input_nodata, inplace=True)
         return data_obj
 
-    @property
-    def crs(self):
-        """:obj:`rasterio.crs.CRS`:
-            Retrieve projection from `xarray.DataArray`
-        """
-        if self._crs is not None:
-            return None if self._crs is False else self._crs
-
-        try:
-            # look in grid_mapping
-            grid_mapping_coord = self._obj.attrs["grid_mapping"]
-            try:
-                crs_wkt = self._obj.coords[grid_mapping_coord].attrs["spatial_ref"]
-            except KeyError:
-                crs_wkt = self._obj.coords[grid_mapping_coord].attrs["crs_wkt"]
-            self.set_crs(crs_wkt, inplace=True)
-        except KeyError:
-            try:
-                # look in attrs for 'crs'
-                self.set_crs(self._obj.attrs["crs"], inplace=True)
-            except KeyError:
-                self._crs = False
-                return None
-        return self._crs
-
     @property
     def encoded_nodata(self):
         """Return the encoded nodata value for the dataset if encoded."""
@@ -1186,11 +1174,19 @@ class RasterDataset(XRasterBase):
         """:obj:`rasterio.crs.CRS`:
             Retrieve projection from `xarray.Dataset`
         """
-        if self._crs is None:
-            try:
-                self._crs = self._obj[self.vars[0]].rio.crs
-            except IndexError:
-                pass
+        if self._crs is not None:
+            return None if self._crs is False else self._crs
+        self._crs = super().crs
+        if self._crs is not None:
+            return self._crs
+        for var in self.vars:
+            crs = self._obj[var].rio.crs
+            if crs is not None:
+                self._crs = crs
+                break
+        else:
+            self._crs = False
+            return None
         return self._crs
 
     def reproject(
diff --git a/setup.py b/setup.py
index f4f9a007..23ad2322 100644
--- a/setup.py
+++ b/setup.py
@@ -61,4 +61,5 @@ setup(
     url="https://github.com/corteva/rioxarray",
     version=get_version(),
     zip_safe=False,
+    python_requires=">=3",
 )
diff --git a/sphinx/examples/crs_management.ipynb b/sphinx/examples/crs_management.ipynb
index 7af5748b..92bb1bef 100644
--- a/sphinx/examples/crs_management.ipynb
+++ b/sphinx/examples/crs_management.ipynb
@@ -20,7 +20,7 @@
    "source": [
     "If you have opened up a dataset and the Coordinate Reference System (CRS) is able to be determined, you can access it via the `rio.crs` accessor.\n",
     "\n",
-    "#### Search order for the CRS (DataArray):\n",
+    "#### Search order for the CRS (DataArray and Dataset):\n",
     "1. Look in attributes (`attrs`) of your data array for the `grid_mapping` coordinate name.\n",
     "   Inside the `grid_mapping` coordinate first look for `spatial_ref` and second for `crs_wkt`.\n",
     "   This is in line with the Climate and Forecast (CF) conventions for storing the CRS.\n",
@@ -28,8 +28,9 @@
     "\n",
     "The value for the `crs` is anything accepted by `rasterio.crs.CRS.from_user_input()`\n",
     "\n",
-    "#### Search order for the CRS (Dataset):\n",
-    "At this time, it pulls the CRS from the first variable of the dataset. This method for accessing the CRS is not recommended."
+    "#### Search order for the CRS for Dataset:\n",
+    "If the CRS is not found using the search methods above, it also searches the `data_vars` and uses the\n",
+    "first valid CRS found."
    ]
   },
   {
@@ -153,7 +154,7 @@
    "source": [
     "## Setting the CRS of the Dataset\n",
     "\n",
-    "Currently, you can use the `rio.set_crs` and `rio.write_crs` methods to set the CRS on your `xarray.Dataset` or `xarray.DataArray`. However, it is recommended to do CRS operations on the `xarray.DataArray`."
+    "Currently, you can use the `rio.set_crs` and `rio.write_crs` methods to set the CRS on your `xarray.Dataset` or `xarray.DataArray`."
    ]
   },
   {
diff --git a/sphinx/history.rst b/sphinx/history.rst
index 6117a2e3..d77191b6 100644
--- a/sphinx/history.rst
+++ b/sphinx/history.rst
@@ -1,6 +1,10 @@
 History
 =======
 
+0.0.13
+------
+- Improve CRS searching for xarray.Dataset & use default grid mapping name (pull #51)
+
 0.0.12
 ------
 - Use `xarray.open_rasterio()` for `rioxarray.open_rasterio()` with xarray<0.12.3 (pull #40)


Test patch:
diff --git a/test/integration/test_integration_rioxarray.py b/test/integration/test_integration_rioxarray.py
index 78532b6a..17c6d026 100644
--- a/test/integration/test_integration_rioxarray.py
+++ b/test/integration/test_integration_rioxarray.py
@@ -1002,6 +1002,25 @@ def test_crs_get_custom():
     assert test_ds.rio.crs.wkt == CustomCRS().wkt
 
 
+def test_get_crs_dataset():
+    test_ds = xarray.Dataset()
+    test_ds = test_ds.rio.write_crs(4326)
+    assert test_ds.attrs["grid_mapping"] == "spatial_ref"
+    assert test_ds.rio.crs.to_epsg() == 4326
+
+
+def test_get_crs_dataset__nonstandard_grid_mapping():
+    test_ds = xarray.Dataset()
+    test_ds = test_ds.rio.write_crs(4326, grid_mapping_name="frank")
+    assert test_ds.attrs["grid_mapping"] == "frank"
+    assert test_ds.rio.crs.to_epsg() == 4326
+
+
+def test_get_crs_dataset__missing_grid_mapping_default():
+    test_ds = xarray.open_dataset(os.path.join(TEST_INPUT_DATA_DIR, "test_find_crs.nc"))
+    assert test_ds.rio.crs.to_epsg() == 32614
+
+
 def test_nodata_setter():
     test_da = xarray.DataArray(
         numpy.zeros((5, 5)),