Repository: SciTools/cartopy

Issue:
ValueError out of geoaxes.set_extent
I've run into an unexpected error out of the set_extent method, and it may just be me abusing the interface, but it could be a bug.

If you have CIS installed to produce the mock data this should reproduce the error:

```
from cis.test.util.mock import make_regular_2d_ungridded_data
import matplotlib.pyplot as plt
import cartopy.crs as ccrs


grenwich_centered_data = make_regular_2d_ungridded_data(lon_dim_length=90, lon_min=-175., lon_max=145., lat_min=-90, lat_max=90)

proj = ccrs.PlateCarree(central_longitude=180)

ax = plt.axes(projection=proj)

im = ax.contour(grenwich_centered_data.coord('longitude').points, 
                grenwich_centered_data.coord('latitude').points,
                grenwich_centered_data.data, 60, proj=ccrs.PlateCarree)

ax.set_extent([0, 360, -90, 90])
```

The error is:

```

---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-5-d9d3e41f907b> in <module>()
     14                 grenwich_centered_data.data, 60)
     15 
---> 16 ax.set_extent([0, 360, -90, 90])

/Users/watson-parris/anaconda/envs/cis_test/lib/python2.7/site-packages/cartopy/mpl/geoaxes.pyc in set_extent(self, extents, crs)
    586         if projected is None:
    587             projected = self.projection.project_geometry(domain_in_crs, crs)
--> 588         x1, y1, x2, y2 = projected.bounds
    589         self.set_xlim([x1, x2])
    590         self.set_ylim([y1, y2])

ValueError: need more than 0 values to unpack
```

Passing crs=proj to the method does return the expected extent, but I wonder if this error might be a common one which should be caught?

A little bit of digging suggests that line 587 returns an empty multiline segment, hence the value error. I'm not sure why that would be though.



Discussion:
Since you are working with a PlateCarree projection you shouldn't really be using values of longitude outside the range [-180, 180]. Remember that you are setting the extent in _projection coordinates_ not degrees, and the PlateCarree projection has extents [-180, 180] regardless of where the meridian is. If you set your extent with

``` python
ax.set_extent([-180, 180, -90, 90])
```

You will get what you want. For the global extent you could also use `ax.set_global()`.

As a side note, there are a couple of minor errors in your example. You should be passing the keyword `transform=ccrs.PlateCarree()` to `contour`, you passed a keyword called `proj` which as far as I can tell is undocumented in matplotlib. You also should make sure you create an instance of the projection class (`PlateCarree()` instead  of `PlateCarree`).

Having said all that, this case at least needs a decent error message, the unhandled ValueError is not useful.


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
@@ -585,7 +585,18 @@ def set_extent(self, extents, crs=None):
 
         if projected is None:
             projected = self.projection.project_geometry(domain_in_crs, crs)
-        x1, y1, x2, y2 = projected.bounds
+        try:
+            # This might fail with an unhelpful error message ('need more
+            # than 0 values to unpack') if the specified extents fall outside
+            # the projection extents, so try and give a better error message.
+            x1, y1, x2, y2 = projected.bounds
+        except ValueError:
+            msg = ('Failed to determine the required bounds in projection '
+                   'coordinates. Check that the values provided are within '
+                   'the valid range (x_limits=[{xlim[0]}, {xlim[1]}], '
+                   'y_limits=[{ylim[0]}, {ylim[1]}]).')
+            raise ValueError(msg.format(xlim=self.projection.x_limits,
+                                        ylim=self.projection.y_limits))
         self.set_xlim([x1, x2])
         self.set_ylim([y1, y2])
 


Test patch: