Repository: Unidata/MetPy

Issue:
gdxarray() returns unusable objects from "thinned GFS" files
Here's example 1:
```python
from datetime import datetime

from metpy.io import GempakGrid
import metpy

print(metpy.__version__)

gem_file_name = '/ldmdata/gempak/model/gfs/21092206_thin.gem'
gem_file = GempakGrid(gem_file_name)

plot_time = datetime(2021, 9, 25, 6)
ht = gem_file.gdxarray(parameter='HGHT', date_time=plot_time, level=500)[0]
print(ht)
```

And the result:
```
1.1.0.post39+gd43d5eb02.d20210825
Traceback (most recent call last):
  File "/home/decker/classes/met433/new_labs/lab3q3.py", line 13, in <module>
    print(ht)
  File "/home/decker/local/miniconda3/envs/devel21/lib/python3.9/site-packages/xarray/core/common.py", line 144, in __repr__
    return formatting.array_repr(self)
  File "/home/decker/local/miniconda3/envs/devel21/lib/python3.9/site-packages/xarray/core/formatting.py", line 529, in array_repr
    summary.append(repr(arr.coords))
  File "/home/decker/local/miniconda3/envs/devel21/lib/python3.9/site-packages/xarray/core/coordinates.py", line 79, in __repr__
    return formatting.coords_repr(self)
  File "/home/decker/local/miniconda3/envs/devel21/lib/python3.9/site-packages/xarray/core/formatting.py", line 422, in coords_repr
    return _mapping_repr(
  File "/home/decker/local/miniconda3/envs/devel21/lib/python3.9/site-packages/xarray/core/formatting.py", line 397, in _mapping_repr
    summary += [summarizer(k, v, col_width) for k, v in mapping.items()]
  File "/home/decker/local/miniconda3/envs/devel21/lib/python3.9/site-packages/xarray/core/formatting.py", line 397, in <listcomp>
    summary += [summarizer(k, v, col_width) for k, v in mapping.items()]
  File "/home/decker/local/miniconda3/envs/devel21/lib/python3.9/site-packages/xarray/core/formatting.py", line 334, in summarize_coord
    return summarize_variable(name, var.variable, col_width, marker)
  File "/home/decker/local/miniconda3/envs/devel21/lib/python3.9/site-packages/xarray/core/formatting.py", line 292, in summarize_variable
    values_str = inline_variable_array_repr(var, values_width)
  File "/home/decker/local/miniconda3/envs/devel21/lib/python3.9/site-packages/xarray/core/formatting.py", line 260, in inline_variable_array_repr
    return format_array_flat(var, max_width)
  File "/home/decker/local/miniconda3/envs/devel21/lib/python3.9/site-packages/xarray/core/formatting.py", line 178, in format_array_flat
    relevant_front_items = format_items(
  File "/home/decker/local/miniconda3/envs/devel21/lib/python3.9/site-packages/xarray/core/formatting.py", line 165, in format_items
    formatted = [format_item(xi, timedelta_format) for xi in x]
  File "/home/decker/local/miniconda3/envs/devel21/lib/python3.9/site-packages/xarray/core/formatting.py", line 165, in <listcomp>
    formatted = [format_item(xi, timedelta_format) for xi in x]
  File "/home/decker/local/miniconda3/envs/devel21/lib/python3.9/site-packages/xarray/core/formatting.py", line 148, in format_item
    return str(x)
  File "/home/decker/src/git_repos/metpy/src/metpy/plots/mapping.py", line 99, in __str__
    return 'Projection: ' + self._attrs['grid_mapping_name']
KeyError: 'grid_mapping_name'
```

Example 2:
```python
from datetime import datetime

from metpy.io import GempakGrid
from metpy.plots import ContourPlot, MapPanel, PanelContainer
import metpy

print(metpy.__version__)

gem_file_name = '/ldmdata/gempak/model/gfs/21092206_thin.gem'
gem_file = GempakGrid(gem_file_name)

plot_time = datetime(2021, 9, 25, 6)
ht = gem_file.gdxarray(parameter='HGHT', date_time=plot_time, level=500)[0]

hts = ContourPlot()
hts.data = ht
hts.contours = list(range(0, 6000, 60))

mp = MapPanel()
mp.plots = [hts]

pc = PanelContainer()
pc.size = (10,10)
pc.panels = [mp]
pc.show()
```

And the output:
```
1.1.0.post39+gd43d5eb02.d20210825
Traceback (most recent call last):
  File "/home/decker/classes/met433/new_labs/lab3q3.py", line 26, in <module>
    pc.show()
  File "/home/decker/src/git_repos/metpy/src/metpy/plots/declarative.py", line 589, in show
    self.draw()
  File "/home/decker/src/git_repos/metpy/src/metpy/plots/declarative.py", line 576, in draw
    panel.draw()
  File "/home/decker/src/git_repos/metpy/src/metpy/plots/declarative.py", line 846, in draw
    p.draw()
  File "/home/decker/src/git_repos/metpy/src/metpy/plots/declarative.py", line 1137, in draw
    self._build()
  File "/home/decker/src/git_repos/metpy/src/metpy/plots/declarative.py", line 1302, in _build
    kwargs = self.parent.plot_kwargs
  File "/home/decker/src/git_repos/metpy/src/metpy/plots/declarative.py", line 814, in plot_kwargs
    dataproj = self.plots[0].griddata.metpy.cartopy_crs
  File "/home/decker/src/git_repos/metpy/src/metpy/xarray.py", line 242, in cartopy_crs
    return self.crs.to_cartopy()
  File "/home/decker/src/git_repos/metpy/src/metpy/plots/mapping.py", line 79, in to_cartopy
    proj_name = self._attrs['grid_mapping_name']
KeyError: 'grid_mapping_name'
```

My expectation is that I get a printout or plot of the data.

Discussion:

Patch:
diff --git a/src/metpy/plots/mapping.py b/src/metpy/plots/mapping.py
index 7a241b4ca2..0ee2f87538 100644
--- a/src/metpy/plots/mapping.py
+++ b/src/metpy/plots/mapping.py
@@ -43,6 +43,11 @@ class CFProjection:
         return {cartopy_name: source[cf_name] for cartopy_name, cf_name in mapping
                 if cf_name in source}
 
+    @property
+    def name(self):
+        """Return the name of the projection."""
+        return self._attrs.get('grid_mapping_name', 'unknown')
+
     @property
     def cartopy_globe(self):
         """Initialize a `cartopy.crs.Globe` from the metadata."""
@@ -76,11 +81,10 @@ class CFProjection:
     def to_cartopy(self):
         """Convert to a CartoPy projection."""
         globe = self.cartopy_globe
-        proj_name = self._attrs['grid_mapping_name']
         try:
-            proj_handler = self.projection_registry[proj_name]
+            proj_handler = self.projection_registry[self.name]
         except KeyError:
-            raise ValueError(f'Unhandled projection: {proj_name}') from None
+            raise ValueError(f'Unhandled projection: {self.name}') from None
 
         return proj_handler(self._attrs, globe)
 
@@ -96,7 +100,7 @@ class CFProjection:
 
     def __str__(self):
         """Get a string representation of the projection."""
-        return 'Projection: ' + self._attrs['grid_mapping_name']
+        return f'Projection: {self.name}'
 
     def __getitem__(self, item):
         """Return a given attribute."""


Test patch:
diff --git a/tests/plots/test_mapping.py b/tests/plots/test_mapping.py
index 3a13a83bd4..1f476c4291 100644
--- a/tests/plots/test_mapping.py
+++ b/tests/plots/test_mapping.py
@@ -54,6 +54,30 @@ def test_bad_projection_raises():
     assert 'Unhandled projection' in str(exc.value)
 
 
+def test_unhandled_projection():
+    """Test behavior when given a projection with no CF equivalent from PROJ."""
+    attrs = {
+        'crs_wkt': 'PROJCRS["unknown",BASEGEOGCRS["unknown",DATUM["unknown",ELLIPSOID['
+                   '"unknown",6371200,0,LENGTHUNIT["metre",1,ID["EPSG",9001]]]],'
+                   'PRIMEM["Greenwich",0,ANGLEUNIT["degree",0.0174532925199433],ID["EPSG",'
+                   '8901]]],CONVERSION["unknown",METHOD["Equidistant Cylindrical ('
+                   'Spherical)",ID["EPSG",1029]],PARAMETER["Latitude of 1st standard '
+                   'parallel",0,ANGLEUNIT["degree",0.0174532925199433],ID["EPSG",8823]],'
+                   'PARAMETER["Longitude of natural origin",0,ANGLEUNIT["degree",'
+                   '0.0174532925199433],ID["EPSG",8802]],PARAMETER["False easting",0,'
+                   'LENGTHUNIT["metre",1],ID["EPSG",8806]],PARAMETER["False northing",0,'
+                   'LENGTHUNIT["metre",1],ID["EPSG",8807]]],CS[Cartesian,2],AXIS["(E)",east,'
+                   'ORDER[1],LENGTHUNIT["metre",1,ID["EPSG",9001]]],AXIS["(N)",north,'
+                   'ORDER[2],LENGTHUNIT["metre",1,ID["EPSG",9001]]]]'}
+    cfproj = CFProjection(attrs)
+
+    assert str(cfproj) == 'Projection: unknown'
+    with pytest.raises(ValueError) as exc:
+        cfproj.to_cartopy()
+
+    assert 'Unhandled projection' in str(exc.value)
+
+
 def test_globe():
     """Test handling building a cartopy globe."""
     attrs = {'grid_mapping_name': 'lambert_conformal_conic', 'earth_radius': 6367000,