Repository: SciTools/cartopy

Issue:
Instantiating from class cartopy.mpl.geoaxes.GeoAxes(*args, **kwargs)[source]
### Description
Using
`vAxes = cartopy.mpl.geoaxes.GeoAxes(projection=ccrs.Mercator())`

throws `KeyError`:

```
File "/usr/lib64/python3.10/site-packages/cartopy/mpl/geoaxes.py", line 410, in __init__
self.projection = kwargs.pop('map_projection')
KeyError: 'map_projection'
```

Note that the following _does_ work:
```
vAxes = matplotlib.pyplot.axes(projection=ccrs.Mercator())

```
Although this throws a warning `QSocketNotifier: Can only be used with threads started with QThread` - which is a different issue?

#### Code to reproduce

```
import cartopy.crs as ccrs
import cartopy.mpl.geoaxes as cga
import cartopy.feature as cfeature

vAxes = cga.GeoAxes(projection=ccrs.Mercator())
```

#### Traceback 

```
File "/usr/lib64/python3.10/site-packages/cartopy/mpl/geoaxes.py", line 410, in __init__
self.projection = kwargs.pop('map_projection')
KeyError: 'map_projection'
```

<details>
 <summary>Full environment definition</summary>

<!-- fill in the following information as appropriate -->

### Operating system
Fedora 35


### Cartopy version
0.20.1

</details>



Discussion:
I have run into the same issue before and I wonder if there is a good reason why there is a "map_projection" keyword here rather than just using "projection" and validating that it is a valid instance of Projection or something similar.

It also lists this as an "optional" keyword, but as this error shows, it isn't so optional ;)
https://github.com/SciTools/cartopy/blob/591fb5450e11b42b6de1cebe4f240112f915bd52/lib/cartopy/mpl/geoaxes.py#L400-L403

Resolving patch:
diff --git a/examples/miscellanea/axes_grid_basic.py b/examples/miscellanea/axes_grid_basic.py
--- a/examples/miscellanea/axes_grid_basic.py
+++ b/examples/miscellanea/axes_grid_basic.py
@@ -41,7 +41,7 @@ def sample_data_3d(shape):
 def main():
     projection = ccrs.PlateCarree()
     axes_class = (GeoAxes,
-                  dict(map_projection=projection))
+                  dict(projection=projection))
 
     lons, lats, times, data = sample_data_3d((6, 73, 145))
 
diff --git a/lib/cartopy/crs.py b/lib/cartopy/crs.py
--- a/lib/cartopy/crs.py
+++ b/lib/cartopy/crs.py
@@ -775,7 +775,7 @@ def _repr_html_(self):
 
     def _as_mpl_axes(self):
         import cartopy.mpl.geoaxes as geoaxes
-        return geoaxes.GeoAxes, {'map_projection': self}
+        return geoaxes.GeoAxes, {'projection': self}
 
     def project_geometry(self, geometry, src_crs=None):
         """
diff --git a/lib/cartopy/mpl/geoaxes.py b/lib/cartopy/mpl/geoaxes.py
--- a/lib/cartopy/mpl/geoaxes.py
+++ b/lib/cartopy/mpl/geoaxes.py
@@ -399,16 +399,22 @@ def __init__(self, *args, **kwargs):
 
         Parameters
         ----------
-        map_projection: optional
-            The target :class:`~cartopy.crs.Projection` of this Axes object.
-
-
-        All other args and keywords are passed through to
-        :class:`matplotlib.axes.Axes`.
+        projection : cartopy.crs.Projection
+            The target projection of this Axes.
+        """
+        if "map_projection" in kwargs:
+            warnings.warn("The `map_projection` keyword argument is "
+                          "deprecated, use `projection` to instantiate a "
+                          "GeoAxes instead.")
+            projection = kwargs.pop("map_projection")
+        else:
+            projection = kwargs.pop("projection")
 
-        """
-        self.projection = kwargs.pop('map_projection')
-        """The :class:`cartopy.crs.Projection` of this GeoAxes."""
+        # The :class:`cartopy.crs.Projection` of this GeoAxes.
+        if not isinstance(projection, ccrs.Projection):
+            raise ValueError("A GeoAxes can only be created with a "
+                             "projection of type cartopy.crs.Projection")
+        self.projection = projection
 
         super().__init__(*args, **kwargs)
         self._gridliners = []


Test patch:
diff --git a/lib/cartopy/tests/mpl/test_axes.py b/lib/cartopy/tests/mpl/test_axes.py
--- a/lib/cartopy/tests/mpl/test_axes.py
+++ b/lib/cartopy/tests/mpl/test_axes.py
@@ -98,7 +98,7 @@ class Test_Axes_add_geometries:
     @mock.patch('cartopy.feature.ShapelyFeature')
     def test_styler_kwarg(self, ShapelyFeature, add_feature_method):
         ax = GeoAxes(plt.figure(), [0, 0, 1, 1],
-                     map_projection=ccrs.Robinson())
+                     projection=ccrs.Robinson())
         ax.add_geometries(mock.sentinel.geometries, mock.sentinel.crs,
                           styler=mock.sentinel.styler, wibble='wobble')