Repository: holoviz/hvplot

Issue:
specifying crs as proj4 string
I can specify the crs as EPSG code, but with my current software configuration crs as proj4 string is failing.   Is this a bug or user error?

When I feed `+init=epsg:26911` as the `crs` parameter, I get:
```
AttributeError: 'Proj' object has no attribute 'is_latlong'
ValueError: Could not parse EPSG code as CRS, must be of the format 'proj4: {proj4 string}.'
ValueError: '+init=epsg:26911' must be either a valid crs or an reference to a `data.attr` containing a valid crs.
```
Cells [14,15] in this reproducible notebook demonstrates the issue:
https://nbviewer.jupyter.org/gist/rsignell-usgs/f28adc0e80fc3c2b29444ff6a26f21d4

Software versions:
```
# Name                    Version                   Build  Channel
bokeh                     1.3.4                    py37_0    conda-forge
datashader                0.7.0                      py_1    conda-forge
holoviews                 1.12.1                     py_2    conda-forge
hvplot                    0.4.0                      py_1    conda-forge
geoviews                  1.6.3                      py_0    conda-forge
geoviews-core             1.6.3                      py_0    conda-forge
cartopy                   0.17.0          py37he1be148_1005    conda-forge
```

Full traceback:
```
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
/srv/conda/envs/notebook/lib/python3.7/site-packages/hvplot/util.py in process_crs(crs)
    175         try:
--> 176             crs = proj_to_cartopy(crs)
    177         except:

/srv/conda/envs/notebook/lib/python3.7/site-packages/hvplot/util.py in proj_to_cartopy(proj)
     81 
---> 82     if proj.is_latlong():
     83         return ccrs.PlateCarree()

AttributeError: 'Proj' object has no attribute 'is_latlong'

During handling of the above exception, another exception occurred:

ValueError                                Traceback (most recent call last)
/srv/conda/envs/notebook/lib/python3.7/site-packages/hvplot/converter.py in _process_crs(self, data, crs)
    395         try:
--> 396             return process_crs(_crs)
    397         except ValueError:

/srv/conda/envs/notebook/lib/python3.7/site-packages/hvplot/util.py in process_crs(crs)
    177         except:
--> 178             raise ValueError("Could not parse EPSG code as CRS, must be of the format 'proj4: {proj4 string}.'")
    179     elif not isinstance(crs, ccrs.CRS):

ValueError: Could not parse EPSG code as CRS, must be of the format 'proj4: {proj4 string}.'

During handling of the above exception, another exception occurred:

ValueError                                Traceback (most recent call last)
<ipython-input-15-235f34bf5baa> in <module>
----> 1 gv.tile_sources.OSM  * da.hvplot.quadmesh(rasterize=True, crs=crs, colormap='viridis').opts(alpha=0.7)

/srv/conda/envs/notebook/lib/python3.7/site-packages/hvplot/__init__.py in quadmesh(self, x, y, z, colorbar, **kwds)
    577             The HoloViews representation of the plot.
    578         """
--> 579         return self(x, y, z=z, kind='quadmesh', colorbar=colorbar, **kwds)
    580 
    581     def contour(self, x=None, y=None, z=None, colorbar=True, **kwds):

/srv/conda/envs/notebook/lib/python3.7/site-packages/hvplot/__init__.py in __call__(self, x, y, kind, **kwds)
    181         HoloViews object: Object representing the requested visualization
    182         """
--> 183         return self._get_converter(x, y, kind, **kwds)(kind, x, y)
    184 
    185     def _get_converter(self, x=None, y=None, kind=None, **kwds):

/srv/conda/envs/notebook/lib/python3.7/site-packages/hvplot/__init__.py in _get_converter(self, x, y, kind, **kwds)
    189         kind = kind or params.pop('kind', None)
    190         return HoloViewsConverter(
--> 191             self._data, x, y, kind=kind, **params
    192         )
    193 

/srv/conda/envs/notebook/lib/python3.7/site-packages/hvplot/converter.py in __init__(self, data, x, y, kind, by, use_index, group_label, value_label, backlog, persist, use_dask, crs, fields, groupby, dynamic, width, height, shared_axes, grid, legend, rot, title, xlim, ylim, clim, xticks, yticks, logx, logy, loglog, hover, subplots, label, invert, stacked, colorbar, fontsize, colormap, datashade, rasterize, row, col, figsize, debug, xaxis, yaxis, framewise, aggregator, projection, global_extent, geo, precompute, flip_xaxis, flip_yaxis, dynspread, hover_cols, x_sampling, y_sampling, project, xlabel, ylabel, xformatter, yformatter, tools, padding, **kwds)
    238         self.dynamic = dynamic
    239         self.geo = geo or crs or global_extent or projection or project
--> 240         self.crs = self._process_crs(data, crs) if self.geo else None
    241         self.project = project
    242         self.row = row

/srv/conda/envs/notebook/lib/python3.7/site-packages/hvplot/converter.py in _process_crs(self, data, crs)
    400                 raise ValueError(
    401                     "'{}' must be either a valid crs or an reference to "
--> 402                     "a `data.attr` containing a valid crs.".format(crs))
    403 
    404 

ValueError: '+init=epsg:26911' must be either a valid crs or an reference to a `data.attr` containing a valid crs.
​```


Discussion:
What happens if you just use: ``'epsg:26911'``
@philippjfr, yes, that works, but is that using the proj4 syntax?
Probably not, no. I would expect `'proj4: +init=epsg:26911'` to work but it doesn't seem to either.
I suspect it's this https://github.com/pytroll/pycoast/issues/27
>I suspect it's this pytroll/pycoast#27

Good find, that seems to be it.
I can open a PR



Resolving patch:
diff --git a/hvplot/util.py b/hvplot/util.py
--- a/hvplot/util.py
+++ b/hvplot/util.py
@@ -31,10 +31,11 @@ def check_crs(crs):
     --------
     >>> p = check_crs('+units=m +init=epsg:26915')
     >>> p.srs
-    '+units=m +init=epsg:26915 '
+    '+proj=utm +zone=15 +datum=NAD83 +units=m +no_defs'
     >>> p = check_crs('wrong')
     >>> p is None
     True
+
     Returns
     -------
     A valid crs if possible, otherwise None
@@ -79,7 +80,10 @@ def proj_to_cartopy(proj):
 
     proj = check_crs(proj)
 
-    if proj.is_latlong():
+    if hasattr(proj, 'crs'):
+        if proj.crs.is_geographic:
+            return ccrs.PlateCarree()
+    elif proj.is_latlong():  # pyproj<2.0
         return ccrs.PlateCarree()
 
     srs = proj.srs


Test patch:
diff --git a/hvplot/tests/testutil.py b/hvplot/tests/testutil.py
--- a/hvplot/tests/testutil.py
+++ b/hvplot/tests/testutil.py
@@ -190,3 +190,20 @@ def test_process_xarray_dataset_with_x_as_derived_datetime(self):
         assert y == 'air'
         assert not by
         assert not groupby
+
+
+class TestGeoUtil(TestCase):
+
+    def setUp(self):
+        try:
+            import geoviews  # noqa
+            import cartopy.crs as ccrs
+        except:
+            raise SkipTest('geoviews or cartopy not available')
+        self.ccrs = ccrs
+
+    def test_proj_to_cartopy(self):
+        from ..util import proj_to_cartopy
+        crs = proj_to_cartopy('+init=epsg:26911')
+
+        assert isinstance(crs, self.ccrs.CRS)