Repository: SciTools/cartopy

Issue:
Cannot make contour plot together with coastlines
### Description

Hello lovely cartopy developers!

Thanks a lot for your great work here!

For some reasons, using `Geoaxes.contour` and `Geoaxes.coastlines` together fails with `AttributeError: 'list' object has no attribute 'xy'` (within shapely though, but I suppose the error lies within cartopy). Excluding either the `ax.contour` call or `ax.coastlines` call down below, or adding `ax.set_global()` before the `plt.savefig` call works. So it seems like cartopy is unable to determine the extent with the two polygons plotted on the `GeoAxes`.


#### Code to reproduce
Take the file at [psyplot/psy-maps/tests/test-t2m-u-v.nc](https://github.com/psyplot/psy-maps/blob/master/tests/test-t2m-u-v.nc) for instance.

```python
import cartopy.crs as ccrs
import matplotlib.pyplot as plt
import xarray as xr
ds = xr.open_dataset('test-t2m-u-v.nc')
ax = plt.axes(projection=ccrs.Orthographic())
ax.coastlines()
ax.contour(ds.lon.values, ds.lat.values, ds.t2m[0, 0].values, transform=ccrs.PlateCarree())
plt.savefig("test.pdf")

```

#### Traceback 

```python
Traceback (most recent call last):
  File "error.py", line 9, in <module>
    plt.savefig("test.pdf")
  File "/home/psommer/miniconda3/envs/test/lib/python3.8/site-packages/matplotlib/pyplot.py", line 859, in savefig
    res = fig.savefig(*args, **kwargs)
  File "/home/psommer/miniconda3/envs/test/lib/python3.8/site-packages/matplotlib/figure.py", line 2311, in savefig
    self.canvas.print_figure(fname, **kwargs)
  File "/home/psommer/miniconda3/envs/test/lib/python3.8/site-packages/matplotlib/backend_bases.py", line 2210, in print_figure
    result = print_method(
  File "/home/psommer/miniconda3/envs/test/lib/python3.8/site-packages/matplotlib/backend_bases.py", line 1639, in wrapper
    return func(*args, **kwargs)
  File "/home/psommer/miniconda3/envs/test/lib/python3.8/site-packages/matplotlib/backends/backend_pdf.py", line 2593, in print_pdf
    self.figure.draw(renderer)
  File "/home/psommer/miniconda3/envs/test/lib/python3.8/site-packages/matplotlib/artist.py", line 41, in draw_wrapper
    return draw(artist, renderer, *args, **kwargs)
  File "/home/psommer/miniconda3/envs/test/lib/python3.8/site-packages/matplotlib/figure.py", line 1863, in draw
    mimage._draw_list_compositing_images(
  File "/home/psommer/miniconda3/envs/test/lib/python3.8/site-packages/matplotlib/image.py", line 131, in _draw_list_compositing_images
    a.draw(renderer)
  File "/home/psommer/miniconda3/envs/test/lib/python3.8/site-packages/matplotlib/artist.py", line 41, in draw_wrapper
    return draw(artist, renderer, *args, **kwargs)
  File "/home/psommer/miniconda3/envs/test/lib/python3.8/site-packages/cartopy/mpl/geoaxes.py", line 479, in draw
    return matplotlib.axes.Axes.draw(self, renderer=renderer, **kwargs)
  File "/home/psommer/miniconda3/envs/test/lib/python3.8/site-packages/matplotlib/artist.py", line 41, in draw_wrapper
    return draw(artist, renderer, *args, **kwargs)
  File "/home/psommer/miniconda3/envs/test/lib/python3.8/site-packages/matplotlib/cbook/deprecation.py", line 411, in wrapper
    return func(*inner_args, **inner_kwargs)
  File "/home/psommer/miniconda3/envs/test/lib/python3.8/site-packages/matplotlib/axes/_base.py", line 2747, in draw
    mimage._draw_list_compositing_images(renderer, self, artists)
  File "/home/psommer/miniconda3/envs/test/lib/python3.8/site-packages/matplotlib/image.py", line 131, in _draw_list_compositing_images
    a.draw(renderer)
  File "/home/psommer/miniconda3/envs/test/lib/python3.8/site-packages/matplotlib/artist.py", line 41, in draw_wrapper
    return draw(artist, renderer, *args, **kwargs)
  File "/home/psommer/miniconda3/envs/test/lib/python3.8/site-packages/cartopy/mpl/feature_artist.py", line 152, in draw
    extent = ax.get_extent(feature_crs)
  File "/home/psommer/miniconda3/envs/test/lib/python3.8/site-packages/cartopy/mpl/geoaxes.py", line 730, in get_extent
    p = self._get_extent_geom(crs)
  File "/home/psommer/miniconda3/envs/test/lib/python3.8/site-packages/cartopy/mpl/geoaxes.py", line 775, in _get_extent_geom
    geom_in_crs = proj.project_geometry(geom_in_src_proj,
  File "/home/psommer/miniconda3/envs/test/lib/python3.8/site-packages/cartopy/crs.py", line 218, in project_geometry
    return getattr(self, method_name)(geometry, src_crs)
  File "/home/psommer/miniconda3/envs/test/lib/python3.8/site-packages/cartopy/crs.py", line 354, in _project_polygon
    is_ccw = polygon.exterior.is_ccw
  File "/home/psommer/miniconda3/envs/test/lib/python3.8/site-packages/shapely/geometry/polygon.py", line 88, in is_ccw
    return bool(self.impl['is_ccw'](self))
  File "/home/psommer/miniconda3/envs/test/lib/python3.8/site-packages/shapely/algorithms/cga.py", line 14, in is_ccw_op
    return signed_area(ring) >= 0.0
  File "/home/psommer/miniconda3/envs/test/lib/python3.8/site-packages/shapely/algorithms/cga.py", line 6, in signed_area
    xs, ys = ring.coords.xy
AttributeError: 'list' object has no attribute 'xy'

```

<details>
 <summary>Full environment definition</summary>

I created a fresh conda environment via

```bash
conda create -c conda-forge --override-channels cartopy scipy matplotlib xarray netcdf4 -n test
```

### Operating system
Linux

### Cartopy version

`conda-forge/linux-64::cartopy-0.18.0-py38h88488af_4`

### conda list

```
# packages in environment at /home/psommer/miniconda3/envs/test:
#
# Name                    Version                   Build  Channel
_libgcc_mutex             0.1                 conda_forge    conda-forge
_openmp_mutex             4.5                       1_gnu    conda-forge
bzip2                     1.0.8                h516909a_3    conda-forge
c-ares                    1.16.1               h516909a_3    conda-forge
ca-certificates           2020.6.20            hecda079_0    conda-forge
cartopy                   0.18.0           py38h88488af_4    conda-forge
certifi                   2020.6.20        py38h924ce5b_2    conda-forge
cftime                    1.2.1            py38hab2c0dc_1    conda-forge
curl                      7.71.1               he644dc0_8    conda-forge
cycler                    0.10.0                     py_2    conda-forge
freetype                  2.10.4               he06d7ca_0    conda-forge
geos                      3.8.1                he1b5a44_0    conda-forge
hdf4                      4.2.13            hf30be14_1003    conda-forge
hdf5                      1.10.6          nompi_h1022a3e_1110    conda-forge
jpeg                      9d                   h516909a_0    conda-forge
kiwisolver                1.3.0            py38hbf85e49_0    conda-forge
krb5                      1.17.1               hfafb76e_3    conda-forge
lcms2                     2.11                 hbd6801e_0    conda-forge
ld_impl_linux-64          2.35                 h769bd43_9    conda-forge
libblas                   3.9.0                2_openblas    conda-forge
libcblas                  3.9.0                2_openblas    conda-forge
libcurl                   7.71.1               hcdd3856_8    conda-forge
libedit                   3.1.20191231         he28a2e2_2    conda-forge
libev                     4.33                 h516909a_1    conda-forge
libffi                    3.2.1             he1b5a44_1007    conda-forge
libgcc-ng                 9.3.0               h5dbcf3e_17    conda-forge
libgfortran-ng            9.3.0               he4bcb1c_17    conda-forge
libgfortran5              9.3.0               he4bcb1c_17    conda-forge
libgomp                   9.3.0               h5dbcf3e_17    conda-forge
liblapack                 3.9.0                2_openblas    conda-forge
libnetcdf                 4.7.4           nompi_hefab0ff_106    conda-forge
libnghttp2                1.41.0               h8cfc5f6_2    conda-forge
libopenblas               0.3.12          pthreads_h4812303_1    conda-forge
libpng                    1.6.37               hed695b0_2    conda-forge
libssh2                   1.9.0                hab1572f_5    conda-forge
libstdcxx-ng              9.3.0               h2ae2ef3_17    conda-forge
libtiff                   4.1.0                hc7e4089_6    conda-forge
libwebp-base              1.1.0                h516909a_3    conda-forge
lz4-c                     1.9.2                he1b5a44_3    conda-forge
matplotlib                3.3.2                         0    conda-forge
matplotlib-base           3.3.2            py38h4d1ce4f_1    conda-forge
ncurses                   6.2                  he1b5a44_2    conda-forge
netcdf4                   1.5.4           nompi_py38hec8b9af_103    conda-forge
numpy                     1.19.2           py38hf89b668_1    conda-forge
olefile                   0.46               pyh9f0ad1d_1    conda-forge
openssl                   1.1.1h               h516909a_0    conda-forge
pandas                    1.1.3            py38hddd6c8b_2    conda-forge
pillow                    8.0.1            py38h9776b28_0    conda-forge
pip                       20.2.4                     py_0    conda-forge
proj                      7.1.1                h966b41f_3    conda-forge
pyparsing                 2.4.7              pyh9f0ad1d_0    conda-forge
pyshp                     2.1.2              pyh9f0ad1d_0    conda-forge
python                    3.8.6           h852b56e_0_cpython    conda-forge
python-dateutil           2.8.1                      py_0    conda-forge
python_abi                3.8                      1_cp38    conda-forge
pytz                      2020.1             pyh9f0ad1d_0    conda-forge
readline                  8.0                  he28a2e2_2    conda-forge
scipy                     1.5.2            py38hd9480d8_2    conda-forge
setuptools                49.6.0           py38h924ce5b_2    conda-forge
shapely                   1.7.1            py38hc7361b7_1    conda-forge
six                       1.15.0             pyh9f0ad1d_0    conda-forge
sqlite                    3.33.0               h4cf870e_1    conda-forge
tk                        8.6.10               hed695b0_1    conda-forge
tornado                   6.0.4            py38h1e0a361_2    conda-forge
wheel                     0.35.1             pyh9f0ad1d_0    conda-forge
xarray                    0.16.1                     py_0    conda-forge
xz                        5.2.5                h516909a_1    conda-forge
zlib                      1.2.11            h516909a_1010    conda-forge
zstd                      1.4.5                h6597ccf_2    conda-forge
```

### pip list

```
Package         Version
--------------- -------------------
Cartopy         0.18.0
certifi         2020.6.20
cftime          1.2.1
cycler          0.10.0
kiwisolver      1.3.0
matplotlib      3.3.2
netCDF4         1.5.4
numpy           1.19.2
olefile         0.46
pandas          1.1.3
Pillow          8.0.1
pip             20.2.4
pyparsing       2.4.7
pyshp           2.1.2
python-dateutil 2.8.1
pytz            2020.1
scipy           1.5.2
setuptools      49.6.0.post20201009
Shapely         1.7.1
six             1.15.0
tornado         6.0.4
wheel           0.35.1
xarray          0.16.1
```

</details>



Discussion:
i run into the very same error-message while i was trying to call `ax.add_collection` on a geo-axes.

I found that manually setting x- and y- limits (e.g. `ax.set_xlim(x0, x1)` and `ax.set_ylim(y0,y1)` prior to any plot-call somehow somehow solved it for me.
> i run into the very same error-message while i was trying to call `ax.add_collection` on a geo-axes.
> 
> I found that manually setting x- and y- limits (e.g. `ax.set_xlim(x0, x1)` and `ax.set_ylim(y0,y1)` prior to any plot-call somehow somehow solved it for me.

Same issue. This workaround is fixing it. It happened with contour plots on a map with country borders on and only sometimes depending on the data. I am suspecting its due to contour polygons being outside of the plotting area of the country borders. I am wondering if this is a bug in cartopy or shapely, or if it is a usage error.
Hi all,

Same issue! Definitely seems like a bug. Setting the xlim/ylim didn't fix it for me. Here's simple code for reproduction:

```
import cartopy.crs as ccrs
import matplotlib.pyplot as plt

W,E,S,N = 176.7, 178.2, -38.8, -37.3

fig = plt.figure(figsize=(20,20))
ax1 = plt.axes(projection=ccrs.epsg(3857))
ax1.set_extent([W,E,S,N], crs=ccrs.PlateCarree())
ax1.coastlines(resolution="10m",linewidth=1.0)
```

Making "W" (west) slightly smaller (e.g. 176.2) prevents the error. Or changing the projection.

Conda packages are up-to-date:

matplotlib                3.3.2                h06a4308_0 
cartopy                   0.18.0           py38h9b98833_8    conda-forge

Resolving patch:
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
@@ -1547,6 +1547,14 @@ def contour(self, *args, **kwargs):
         """
         result = matplotlib.axes.Axes.contour(self, *args, **kwargs)
 
+        # We need to compute the dataLim correctly for contours.
+        bboxes = [col.get_datalim(self.transData)
+                  for col in result.collections
+                  if col.get_paths()]
+        if bboxes:
+            extent = mtransforms.Bbox.union(bboxes)
+            self.dataLim.update_from_data_xy(extent.get_points())
+
         self.autoscale_view()
 
         # Re-cast the contour as a GeoContourSet.
diff --git a/lib/cartopy/mpl/gridliner.py b/lib/cartopy/mpl/gridliner.py
--- a/lib/cartopy/mpl/gridliner.py
+++ b/lib/cartopy/mpl/gridliner.py
@@ -437,7 +437,6 @@ def _draw_gridliner(self, nx=None, ny=None, renderer=None):
         sup = min(lat_lim[1], crs.y_limits[1])
         lat_ticks = [value for value in lat_ticks if inf <= value <= sup]
 
-
         #####################
         # Gridlines drawing #
         #####################


Test patch:
diff --git a/lib/cartopy/tests/mpl/baseline_images/mpl/test_mpl_integration/global_contour_wrap.png b/lib/cartopy/tests/mpl/baseline_images/mpl/test_mpl_integration/global_contour_wrap.png
Binary files a/lib/cartopy/tests/mpl/baseline_images/mpl/test_mpl_integration/global_contour_wrap.png and b/lib/cartopy/tests/mpl/baseline_images/mpl/test_mpl_integration/global_contour_wrap.png differ
diff --git a/lib/cartopy/tests/mpl/test_contour.py b/lib/cartopy/tests/mpl/test_contour.py
--- a/lib/cartopy/tests/mpl/test_contour.py
+++ b/lib/cartopy/tests/mpl/test_contour.py
@@ -61,3 +61,15 @@ def test_contour_linear_ring():
     ax.contourf(lons, lats, vals, np.arange(9), transform=ccrs.PlateCarree())
 
     plt.draw()
+
+
+def test_contour_update_bounds():
+    """Test that contour updates the extent"""
+    xs, ys = np.meshgrid(np.linspace(0, 360), np.linspace(-80, 80))
+    zs = ys**2
+    ax = plt.axes(projection=ccrs.Orthographic())
+    ax.contour(xs, ys, zs, transform=ccrs.PlateCarree())
+    # Force a draw, which is a smoke test to make sure contouring
+    # doesn't raise with an Orthographic projection
+    # GH issue 1673
+    plt.draw()
diff --git a/lib/cartopy/tests/mpl/test_mpl_integration.py b/lib/cartopy/tests/mpl/test_mpl_integration.py
--- a/lib/cartopy/tests/mpl/test_mpl_integration.py
+++ b/lib/cartopy/tests/mpl/test_mpl_integration.py
@@ -45,6 +45,10 @@ def test_global_contour_wrap_new_transform():
     x, y = np.meshgrid(np.linspace(0, 360), np.linspace(-90, 90))
     data = np.sin(np.sqrt(x ** 2 + y ** 2))
     plt.contour(x, y, data, transform=ccrs.PlateCarree())
+    if MPL_VERSION <= '3.0.0':
+        # Rather than updating image test for old version
+        # Remove when only MPL > 3 is required
+        ax.set_extent((-176.3265306122449, 176.3265306122449, -90.0, 90.0))
 
 
 @pytest.mark.natural_earth
@@ -55,6 +59,10 @@ def test_global_contour_wrap_no_transform():
     x, y = np.meshgrid(np.linspace(0, 360), np.linspace(-90, 90))
     data = np.sin(np.sqrt(x ** 2 + y ** 2))
     plt.contour(x, y, data)
+    if MPL_VERSION <= '3.0.0':
+        # Rather than updating image test for old version
+        # Remove when only MPL > 3 is required
+        ax.set_extent((-176.3265306122449, 176.3265306122449, -90.0, 90.0))
 
 
 @pytest.mark.natural_earth
diff --git a/lib/cartopy/tests/mpl/test_ticks.py b/lib/cartopy/tests/mpl/test_ticks.py
--- a/lib/cartopy/tests/mpl/test_ticks.py
+++ b/lib/cartopy/tests/mpl/test_ticks.py
@@ -38,6 +38,8 @@ def _format_lon(val, i):
 
 
 ticks_tolerance = 7
+
+
 @pytest.mark.natural_earth
 @ImageTesting(['xticks_no_transform'],
               tolerance=ticks_tolerance)