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:

Resolving patch:
diff --git a/lib/cartopy/feature.py b/lib/cartopy/feature.py
--- a/lib/cartopy/feature.py
+++ b/lib/cartopy/feature.py
@@ -1,4 +1,4 @@
-# (C) British Crown Copyright 2011 - 2015, Met Office
+# (C) British Crown Copyright 2011 - 2016, Met Office
 #
 # This file is part of cartopy.
 #
@@ -29,7 +29,6 @@
 import shapely.geometry as sgeom
 import six
 
-from cartopy.io.ogc_clients import WFSGeometrySource
 import cartopy.io.shapereader as shapereader
 import cartopy.crs
 
@@ -298,6 +297,8 @@ class WFSFeature(Feature):
     A class capable of drawing a collection of geometries
     obtained from an OGC Web Feature Service (WFS).
 
+    This feature requires additional dependencies. If installed via pip,
+    try ``pip install cartopy[ows]``.
     """
     def __init__(self, wfs, features, **kwargs):
         """
@@ -315,6 +316,12 @@ def __init__(self, wfs, features, **kwargs):
             Keyword arguments to be used when drawing this feature.
 
         """
+        try:
+            from cartopy.io.ogc_clients import WFSGeometrySource
+        except ImportError as e:
+            six.raise_from(ImportError(
+                'WFSFeature requires additional dependencies. If installed '
+                'via pip, try `pip install cartopy[ows]`.\n'), e)
 
         self.source = WFSGeometrySource(wfs, features)
         crs = self.source.default_projection()
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
@@ -1,4 +1,4 @@
-# (C) British Crown Copyright 2011 - 2015, Met Office
+# (C) British Crown Copyright 2011 - 2016, Met Office
 #
 # This file is part of cartopy.
 #
@@ -637,7 +637,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:
diff --git a/lib/cartopy/tests/mpl/baseline_images/mpl/test_images/web_tiles.png b/lib/cartopy/tests/mpl/baseline_images/mpl/test_images/web_tiles.png
Binary files a/lib/cartopy/tests/mpl/baseline_images/mpl/test_images/web_tiles.png and b/lib/cartopy/tests/mpl/baseline_images/mpl/test_images/web_tiles.png differ