Repository: holoviz/hvplot

Issue:
`geo=True` should look for `crs` on `xarray.dataset` if none provided
This came out of a conversation with @philippjfr. Potentially `crs` could also accept a string which would refer to an `attr` or I guess a `coord` containing the `crs` info. This would be a little trickier to implement since `crs` already accepts a proj4 string. But I think it'd be nicely parallel to how x, y, and z are set.


Discussion:

Resolving patch:
diff --git a/hvplot/converter.py b/hvplot/converter.py
--- a/hvplot/converter.py
+++ b/hvplot/converter.py
@@ -145,7 +145,7 @@ def __init__(self, data, x, y, kind=None, by=None, use_index=True,
         self.group_label = group_label
         self.dynamic = dynamic
         self.geo = geo or crs or global_extent or projection
-        self.crs = process_crs(crs) if self.geo else None
+        self.crs = self._process_crs(data, crs) if self.geo else None
         self.row = row
         self.col = col
 
@@ -239,6 +239,21 @@ def __init__(self, data, x, y, kind=None, by=None, use_index=True,
                          'y: {y}, by: {by}, groupby: {groupby}'.format(**kwds))
 
 
+    def _process_crs(self, data, crs):
+        """Given crs as proj4 string, data.attr, or cartopy.crs return cartopy.crs
+        """
+        # get the proj string: either the value of data.attrs[crs] or crs itself
+        _crs = getattr(data, 'attrs', {}).get(crs or 'crs', crs)
+        try:
+            return process_crs(_crs)
+        except ValueError:
+            # only raise error if crs was specified in kwargs
+            if crs:
+                raise ValueError(
+                    "'{}' must be either a valid crs or an reference to "
+                    "a `data.attr` containing a valid crs.".format(crs))
+
+
     def _process_data(self, kind, data, x, y, by, groupby, row, col,
                       use_dask, persist, backlog, label, value_label,
                       hover_cols, kwds):


Test patch:
diff --git a/hvplot/tests/testgeo.py b/hvplot/tests/testgeo.py
new file mode 100644
--- /dev/null
+++ b/hvplot/tests/testgeo.py
@@ -0,0 +1,48 @@
+from unittest import TestCase, SkipTest
+
+
+class TestGeo(TestCase):
+    def setUp(self):
+        try:
+            import xarray as xr
+            import rasterio  # noqa
+            import geoviews  # noqa
+            import cartopy.crs as ccrs
+        except:
+            raise SkipTest('xarray, rasterio, geoviews, or cartopy not available')
+        import hvplot.xarray  # noqa
+        self.da = (xr.open_rasterio(
+            'https://github.com/mapbox/rasterio/raw/master/tests/data/RGB.byte.tif')
+            .sel(band=1))
+        self.crs = ccrs.epsg(self.da.crs.split('epsg:')[1])
+
+    def assertCRS(self, plot, proj='utm'):
+        assert plot.crs.proj4_params['proj'] == proj
+
+    def test_plot_with_crs_as_object(self):
+        plot = self.da.hvplot('x', 'y', crs=self.crs)
+        self.assertCRS(plot)
+
+    def test_plot_with_crs_as_proj_string(self):
+        plot = self.da.hvplot('x', 'y', crs=self.da.crs)
+        self.assertCRS(plot)
+
+    def test_plot_with_geo_as_true_crs_undefined(self):
+        plot = self.da.hvplot('x', 'y', geo=True)
+        self.assertCRS(plot)
+
+    def test_plot_with_crs_as_attr_str(self):
+        da = self.da.copy()
+        da.attrs = {'bar': self.crs}
+        plot = da.hvplot('x', 'y', crs='bar')
+        self.assertCRS(plot)
+
+    def test_plot_with_crs_as_nonexistent_attr_str(self):
+        with self.assertRaisesRegex(ValueError, "'foo' must be"):
+            self.da.hvplot('x', 'y', crs='foo')
+
+    def test_plot_with_geo_as_true_crs_no_crs_on_data_returns_default(self):
+        da = self.da.copy()
+        da.attrs = {'bar': self.crs}
+        plot = da.hvplot('x', 'y', geo=True)
+        self.assertCRS(plot, 'eqc')