Repository: SciTools/cartopy

Issue:
Problem with contourf when displaying data with Cartopy
In certain cases (including but not necessarily limited to increasing the # of fill intervals and adding the "extend" option for the colorbar), I end up with a frame that contains just a single color, with no geography showing.

The problem is illustrated with this Jupyter notebook:

http://nbviewer.jupyter.org/url/www.atmos.albany.edu/facstaff/ktyle/notebooks/Cartopy_Issue.ipynb?flush_cache=true

matplotlib: v 1.5.1
cartopy: v0.14.2



Discussion:
I was shown this same problem by @wendiflynn at our workshop this week.

I have run into this issue too. This appears to be a regression in v0.14, I don't get the problem in v0.13. A bisection shows commit d74eeddb22cc98c7a13f0f69b3c8949f0bd00d3a is responsible.

@pelson - I'm not familiar with the change in d74eeddb22cc98c7a13f0f69b3c8949f0bd00d3a can you help out with this please?

Edit: I should have been more specific about my testing. I'm using matplotlib v1.5.1.

It's definitely something funky with the determination of the data limits. I can solve the example from @ktyle above by adding this at the end of one of the problematic cells:

``` python
ax.set_extent([xm.min(), xm.max(), ym.min(), ym.max()], crs=proj_lcc)
```

without that setting, the extent is `(-0.05, 0.05, -0.05, 0.05)`.

I can also get something back that at least looks physical, though the limits now are too large with:

``` python
ax.relim()
ax.autoscale_view()
```

Those two were lifted from #647 .

Well, I managed to boil it down to an example without external data:

``` python
import matplotlib.pyplot as plt
import numpy as np
import cartopy.crs as ccrs

x = np.linspace(-2763217.0, 2681906.0, 200)
y = np.linspace(-263790.62, 3230840.5, 130)
data = np.hypot(*np.meshgrid(x, y)) / 2e5

proj_lcc = ccrs.LambertConformal(central_longitude=-95,
                             central_latitude=25,
                             standard_parallels=[25])

fig, ax = plt.subplots(1, 1, subplot_kw={'projection': proj_lcc})
contours = ax.contourf(x, y, data, levels=np.arange(0, 40, 1))
ax.coastlines()
#ax.set_extent([x.min(), x.max(), y.min(), y.max()], crs=proj_lcc)
```

Gives:
![image](https://cloud.githubusercontent.com/assets/221526/19707797/07ccf7c0-9ad7-11e6-8908-c7a423369b2e.png)

With last line uncommented:
![image](https://cloud.githubusercontent.com/assets/221526/19707801/1230160c-9ad7-11e6-954e-d040c1b8feed.png)

So this line

``` python
            extent = mtransforms.Bbox.union([col.get_datalim(self.transData)
                                             for col in result.collections])
```

is the problem. In the cases where setting the contour causes problems, the extent ends up as `Bbox([[-inf, -inf], [inf, inf]])`. Is there any reason the Bbox should be undefined?

A second question is: the code in CartoPy for `GeoAxes.contour()` doesn't do any of this, but should it? Or should this just be pulled out of `contourf`? There wouldn't seem to be any reason to me that they should be different, but I may be missing something.

Ah, I see--there's a `BBox` problem any time there's a contour created that doesn't actually have any points on the map, so there's not actually a path to draw for the contour (which happens frequently when specifying specific contours).  Could solve that with:

``` python
            extent = mtransforms.Bbox.union([col.get_datalim(self.transData)
                                             for col in result.collections]
                                             if col.get_paths())
```

Any thoughts out there on this solution?

This seems reasonable enough, do you want to make a PR against v0.14.x that implements this? The example you suggest may serve well as a test. I guess we just need to be careful that the behaviour the original fix was designed for is preserved. It would also be worthwhile to decide if we need this for contour as well as contourf, in principle I suppose we do...

I'll start a PR with fix + test for contourf.


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
@@ -1147,7 +1147,8 @@ def contourf(self, *args, **kwargs):
         # We need to compute the dataLim correctly for contours.
         if matplotlib.__version__ >= '1.4':
             extent = mtransforms.Bbox.union([col.get_datalim(self.transData)
-                                             for col in result.collections])
+                                             for col in result.collections
+                                             if col.get_paths()])
             self.dataLim.update_from_data_xy(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
new file mode 100644
--- /dev/null
+++ b/lib/cartopy/tests/mpl/test_contour.py
@@ -0,0 +1,45 @@
+# (C) British Crown Copyright 2016, Met Office
+#
+# This file is part of cartopy.
+#
+# cartopy is free software: you can redistribute it and/or modify it under
+# the terms of the GNU Lesser General Public License as published by the
+# Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# cartopy is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with cartopy.  If not, see <https://www.gnu.org/licenses/>.
+
+from __future__ import (absolute_import, division, print_function)
+
+import matplotlib.pyplot as plt
+from matplotlib.testing.decorators import cleanup
+import numpy as np
+from numpy.testing import assert_array_almost_equal
+
+import cartopy.crs as ccrs
+
+
+@cleanup
+def test_contour_plot_bounds():
+    x = np.linspace(-2763217.0, 2681906.0, 200)
+    y = np.linspace(-263790.62, 3230840.5, 130)
+    data = np.hypot(*np.meshgrid(x, y)) / 2e5
+
+    proj_lcc = ccrs.LambertConformal(central_longitude=-95,
+                                     central_latitude=25,
+                                     standard_parallels=[25])
+    ax = plt.axes(projection=proj_lcc)
+    ax.contourf(x, y, data, levels=np.arange(0, 40, 1))
+    assert_array_almost_equal(ax.get_extent(),
+                              np.array([x[0], x[-1], y[0], y[-1]]))
+
+
+if __name__ == '__main__':
+    import nose
+    nose.runmodule(argv=['-s', '--with-doctest'], exit=False)