Repository: SciTools/cartopy

Issue:
Strange extent behaviour when latitude value too high
### Description
<!-- Please provide a general introduction to the issue/proposal. -->
This problem behaviour was triggered by a downstream bug, so I completely understand if you prefer to ignore this issue!  The bug is that my latitude values were generated with `np.arange`, which resulted in my maximum latitude being slightly greater than 90 degrees.

My code creates a contourf and then adds a box to highlight a particular region.  The extent of the resulting plot shrinks to match the box.   If I remove the "problem line" in the below example, the output image is as expected.  The problem also goes away if the axes is instantiated with `projection=SOURCE_PROJ`.

<!--
If you are reporting a bug, attach the *entire* traceback from Python.

If you are proposing an enhancement/new feature, provide links to related articles, reference examples, etc.

If you are asking a question, please ask on StackOverflow and use the cartopy tag. All cartopy
questions on StackOverflow can be found at https://stackoverflow.com/questions/tagged/cartopy
-->

#### Code to reproduce

```python
import matplotlib.pyplot as plt
import numpy as np

import cartopy.crs as ccrs

SOURCE_PROJ = ccrs.PlateCarree()
TARGET_PROJ = ccrs.PlateCarree(central_longitude=200)

def sample_data(shape=(73, 145)):
    """
    Return ``lons``, ``lats`` and ``data`` of some fake data.
    """
    nlats, nlons = shape
    lats1d = np.linspace(-np.pi / 2, np.pi / 2, nlats)
    lons1d = np.linspace(0, 2 * np.pi, nlons)
    lons, lats = np.meshgrid(lons1d, lats1d)
    wave = 0.75 * (np.sin(2 * lats) ** 8) * np.cos(4 * lons)
    mean = 0.5 * np.cos(2 * lats) * ((np.sin(2 * lats)) ** 2 + 2)

    lats1d = np.rad2deg(lats1d)
    lons1d = np.rad2deg(lons1d)
    data = wave + mean

    lats1d[-1] += 0.00001  # <-- PROBLEM LINE

    return lons1d, lats1d, data


def latlon_region_plot(lat_range, lon_range, ax):
    """
    Add a box described by lat_range and lon_range to the geoaxes.
    """
    ax.plot(
        [lon_range[0], lon_range[1], lon_range[1], lon_range[0], lon_range[0]],
        [lat_range[0], lat_range[0], lat_range[1], lat_range[1], lat_range[0]],
        transform=SOURCE_PROJ, color='magenta')


lons, lats, data = sample_data()
fig = plt.figure()
ax = fig.add_subplot(projection=TARGET_PROJ)

cf = ax.contourf(lons, lats, data, transform=SOURCE_PROJ)
ax.coastlines()
latlon_region_plot([-5, 25], [110, 140], ax)

plt.show()
```

#### Actual Result
![extent_box_bad](https://user-images.githubusercontent.com/10599679/214316084-4592fe04-8a3d-48ee-9a3e-5bea695bb8e0.png)

#### Expected Result
![extent_box_good](https://user-images.githubusercontent.com/10599679/214316152-a5c211fd-4ead-490e-ad37-44f78c7eb834.png)

<details>
 <summary>Full environment definition</summary>

<!-- fill in the following information as appropriate -->

### Operating system
RHEL7

### Cartopy version
0.20.3, 0.21.0, 0.21.1

The problem does not appear with 0.19.

### conda list

```
```

### pip list

```
```

</details>



Discussion:
Thanks to @trexfeathers who spotted the latitude point was the problem.
This only seems to be an issue with contours (changing to pcolormesh the auto-scaling is as expected).
I wonder if this is actually an issue in MPL rather than Cartopy...
when adding the collection with `autolim=False`
https://github.com/matplotlib/matplotlib/blob/66f7956984cbfc3647e867c6e5fde889a89c64ef/lib/matplotlib/contour.py#L872-L877

The autoscaling is as expected if autolim is True when adding the collections.
```python
for col in cf.collections:
    ax.add_collection(col, autolim=True)
```

I couldn't boil it down to an easily reproducible MPL-only example, maybe something to do with non-affine transforms on the collections?
Thanks @greglucas.  I think I've tracked down where my dodgy latitude point causes the problem:

https://github.com/matplotlib/matplotlib/blob/c23ccdde6f0f8c071b09a88770e24452f2859e99/lib/matplotlib/axes/_base.py#L2532-L2537

with the dodgy point, `xys` here is all nans, so the limits don't get updated* and `ax.ignore_existing_data_limits` doesn't get set to `False`.

*Although they must get updated _somewhere_, since the plot looks fine if I don't add the box with `plot`.
For `pcolormesh`, Cartopy has its own call to `update_datalim`, specifically ignoring nans

https://github.com/SciTools/cartopy/blob/a8f62baf12e73921a5910a61f2c7b75d8dfc07fb/lib/cartopy/mpl/geoaxes.py#L1889-L1898

Would it be reasonable to add

```python
self.ignore_existing_data_limits = False
```

after the `contour` and `contourf` limit updates?
https://github.com/SciTools/cartopy/blob/a8f62baf12e73921a5910a61f2c7b75d8dfc07fb/lib/cartopy/mpl/geoaxes.py#L1622
https://github.com/SciTools/cartopy/blob/a8f62baf12e73921a5910a61f2c7b75d8dfc07fb/lib/cartopy/mpl/geoaxes.py#L1670

Or would it be better to do something in Matplotlib?
Aren't we already doing that with the `ignore=False` in the update calls? I think we agree that we should take account of the previous bboxes still and not change those to True (if you add one collection in the western hemisphere and this one is in the eastern hemisphere, we want the full globe not just the eastern hemisphere). But, I think you're proposing to do something else that I'm not following. Maybe a PR/example would be better.

Maybe there is a way to filter out the nans in the paths/bboxes better here? (nanmin used in pcolormesh)

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
@@ -1619,7 +1619,7 @@ def contour(self, *args, **kwargs):
                   if col.get_paths()]
         if bboxes:
             extent = mtransforms.Bbox.union(bboxes)
-            self.dataLim.update_from_data_xy(extent.get_points(), ignore=False)
+            self.update_datalim(extent.get_points())
 
         self.autoscale_view()
 
@@ -1667,7 +1667,7 @@ def contourf(self, *args, **kwargs):
                   if col.get_paths()]
         if bboxes:
             extent = mtransforms.Bbox.union(bboxes)
-            self.dataLim.update_from_data_xy(extent.get_points(), ignore=False)
+            self.update_datalim(extent.get_points())
 
         self.autoscale_view()
 


Test patch:
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
@@ -54,6 +54,24 @@ def test_contour_doesnt_shrink():
     assert_array_almost_equal(ax.get_extent(), expected)
 
 
+@pytest.mark.parametrize('func', ['contour', 'contourf'])
+def test_plot_after_contour_doesnt_shrink(func):
+    xglobal = np.linspace(-180, 180)
+    yglobal = np.linspace(-90, 90.00001)
+
+    data = np.hypot(*np.meshgrid(xglobal, yglobal))
+
+    target_proj = ccrs.PlateCarree(central_longitude=200)
+    source_proj = ccrs.PlateCarree()
+
+    ax = plt.axes(projection=target_proj)
+    test_func = getattr(ax, func)
+    test_func(xglobal, yglobal, data, transform=source_proj)
+    ax.plot([10, 20], [20, 30], transform=source_proj)
+    expected = np.array([xglobal[0], xglobal[-1], yglobal[0], 90])
+    assert_array_almost_equal(ax.get_extent(), expected)
+
+
 def test_contour_linear_ring():
     """Test contourf with a section that only has 3 points."""
     ax = plt.axes([0.01, 0.05, 0.898, 0.85], projection=ccrs.Mercator(),