Repository: SciTools/cartopy

Issue:
Azimuthal Equidistant incorrect for non-polar and global extent
If you use `AzimuthalEquidistant` with non-polar central latitudes (`central_latitude != +/- 90`) and _global_ extent, the projection does not project correctly.

For example, `central_latitude=40, central_longitude=-100` should be something like the following:
![correct](http://www.globmaritime.com/images/stories/users/azimuthal_equidistant_map_of_the_world.gif)

When attempted with Cartopy, the result is:
![figure_1](https://cloud.githubusercontent.com/assets/302469/6312009/7c2b5074-b93a-11e4-9afc-86b39c38157a.png)

I have marked 3 points at (100W, 40N) (correct - green), at (60W, 60S) (probably correct - blue), and at (60E, 60S) (not correct - red). I haven't had a chance to look too deeply, but is there some sort of optimization that prevents the paths from going the "long way" around? Maybe some constraint to [0, 2pi) or something?

```
import matplotlib.pyplot as plt
import cartopy.crs as ccrs

proj = ccrs.AzimuthalEquidistant(central_latitude=40, central_longitude=-100)
ax = plt.axes(projection=proj)

ax.coastlines()
ax.gridlines()

ax.plot(-100, 40, 'go', transform=ccrs.Geodetic())
ax.plot(60, -60, 'ro', transform=ccrs.Geodetic())
ax.plot(-60, -60, 'bo', transform=ccrs.Geodetic())

# Global is as above
ax.set_global()
# Local is mostly okay, but you get wrapped around stuff underneath
#ax.set_extent([-140, -70, 10, 60])

plt.show()
```



Discussion:
Ah, it seems if you set a spherical globe, then things are projected better:
![figure_1](https://cloud.githubusercontent.com/assets/302469/6313395/ebd6ee66-b975-11e4-84c2-a8a825bca5e4.png)

Some searching indicates this may be a [bug in proj4](http://trac.osgeo.org/proj/ticket/246). I guess we should add a warning or document this somewhere?

Looks like [basemap does the same](https://github.com/matplotlib/basemap/blob/master/lib/mpl_toolkits/basemap/proj.py#L112).

Hmmm. Does look like a Proj4 bug. Would you mind feeding that back to proj4 and linking the issues?

The [proj4 issue](http://trac.osgeo.org/proj/ticket/246) already existed with a not-quite-complete patch. Hopefully it will be included soon.

This bug has been fixed in proj 4.9.2, but I'll leave it open because we're not sure if we want to bump the requirements just yet.

> because we're not sure if we want to bump the requirements just yet.

I wouldn't be averse to that. Proj4 seems to have had a good amount of life pumped back into it, and I'm keen to make the most of it being maintained. Obviously we'd need to reflect that in our conda-recipes-scitools build though.

On the other hand, there are some requests to reduce the dependency from 4.9 to 4.8. Maybe a warning with older version like with Robinson?

> Maybe a warning with older version like with Robinson?

Very sensible. I'll put that in v0.14 milestone. Want to take it on?


Resolving patch:
diff --git a/lib/cartopy/crs.py b/lib/cartopy/crs.py
--- a/lib/cartopy/crs.py
+++ b/lib/cartopy/crs.py
@@ -33,7 +33,7 @@
 from shapely.prepared import prep
 import six
 
-from cartopy._crs import CRS, Geocentric, Geodetic, Globe, PROJ4_RELEASE
+from cartopy._crs import CRS, Geocentric, Geodetic, Globe, PROJ4_VERSION
 import cartopy.trace
 
 
@@ -1454,10 +1454,8 @@ def __init__(self, central_longitude=0, globe=None):
         # 40 deg N introduced by incomplete fix to issue #113 (see
         # https://trac.osgeo.org/proj/ticket/113).
         import re
-        match = re.search(r"\d\.\d", PROJ4_RELEASE)
-        if match is not None:
-            proj4_version = float(match.group())
-            if 4.8 <= proj4_version < 4.9:
+        if PROJ4_VERSION != ():
+            if (4, 8) <= PROJ4_VERSION < (4, 9):
                 warnings.warn('The Robinson projection in the v4.8.x series '
                               'of Proj.4 contains a discontinuity at '
                               '40 deg latitude. Use this projection with '
@@ -1748,6 +1746,20 @@ def __init__(self, central_longitude=0.0, central_latitude=0.0,
                       default globe is created.
 
         """
+        # Warn when using Azimuthal Equidistant with proj4 < 4.9.2 due to
+        # incorrect transformation past 90 deg distance (see
+        # https://github.com/OSGeo/proj.4/issues/246).
+        if PROJ4_VERSION != ():
+            if PROJ4_VERSION < (4, 9, 2):
+                warnings.warn('The Azimuthal Equidistant projection in Proj.4 '
+                              'older than 4.9.2 incorrectly transforms points '
+                              'farther than 90 deg from the origin. Use this '
+                              'projection with caution.')
+        else:
+            warnings.warn('Cannot determine Proj.4 version. The Azimuthal '
+                          'Equidistant projection may be unreliable and '
+                          'should be used with caution.')
+
         proj4_params = [('proj', 'aeqd'), ('lon_0', central_longitude),
                         ('lat_0', central_latitude),
                         ('x_0', false_easting), ('y_0', false_northing)]


Test patch:
diff --git a/lib/cartopy/tests/__init__.py b/lib/cartopy/tests/__init__.py
--- a/lib/cartopy/tests/__init__.py
+++ b/lib/cartopy/tests/__init__.py
@@ -24,15 +24,6 @@
 import shutil
 import types
 
-from cartopy._crs import PROJ4_RELEASE as _PROJ4_RELEASE
-
-
-_match = re.search(r"\d\.\d", _PROJ4_RELEASE)
-if _match is not None:
-    _proj4_version = float(_match.group())
-else:
-    _proj4_version = 0.0
-
 
 @contextlib.contextmanager
 def temp_dir(suffix=None):
diff --git a/lib/cartopy/tests/crs/test_robinson.py b/lib/cartopy/tests/crs/test_robinson.py
--- a/lib/cartopy/tests/crs/test_robinson.py
+++ b/lib/cartopy/tests/crs/test_robinson.py
@@ -33,7 +33,6 @@
 from numpy.testing import assert_array_almost_equal as assert_arr_almost_eq
 
 import cartopy.crs as ccrs
-from cartopy.tests import _proj4_version
 
 
 _NAN = float('nan')
@@ -41,7 +40,7 @@
 _CRS_ROB = ccrs.Robinson()
 
 # Increase tolerance if using older proj.4 releases
-_TOL = -1 if _proj4_version < 4.9 else 7
+_TOL = -1 if ccrs.PROJ4_VERSION < (4, 9) else 7
 
 
 def test_transform_point():
diff --git a/lib/cartopy/tests/mpl/test_gridliner.py b/lib/cartopy/tests/mpl/test_gridliner.py
--- a/lib/cartopy/tests/mpl/test_gridliner.py
+++ b/lib/cartopy/tests/mpl/test_gridliner.py
@@ -32,7 +32,6 @@
 from cartopy.mpl.geoaxes import GeoAxes
 from cartopy.mpl.gridliner import LATITUDE_FORMATTER, LONGITUDE_FORMATTER
 
-from cartopy.tests import _proj4_version
 from cartopy.tests.mpl import ImageTesting
 
 
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
@@ -32,11 +32,10 @@
 
 import cartopy.crs as ccrs
 
-from cartopy.tests import _proj4_version
 from cartopy.tests.mpl import ImageTesting
 
 
-_ROB_TOL = 0.5 if _proj4_version < 4.9 else 0.1
+_ROB_TOL = 0.5 if ccrs.PROJ4_VERSION < (4, 9) else 0.1
 
 
 @ImageTesting(['global_contour_wrap'])
@@ -119,7 +118,8 @@ def test_global_scatter_wrap_no_transform():
     plt.scatter(x, y, c=data)
 
 
-@ImageTesting(['global_map'], tolerance=16 if _proj4_version < 4.9 else 0.1)
+@ImageTesting(['global_map'],
+              tolerance=16 if ccrs.PROJ4_VERSION < (4, 9) else 0.1)
 def test_global_map():
     ax = plt.axes(projection=ccrs.Robinson())
 #    ax.coastlines()
diff --git a/lib/cartopy/tests/mpl/test_shapely_to_mpl.py b/lib/cartopy/tests/mpl/test_shapely_to_mpl.py
--- a/lib/cartopy/tests/mpl/test_shapely_to_mpl.py
+++ b/lib/cartopy/tests/mpl/test_shapely_to_mpl.py
@@ -28,7 +28,6 @@
 import cartopy.crs as ccrs
 import cartopy.mpl.patch as cpatch
 
-from cartopy.tests import _proj4_version
 from cartopy.tests.mpl import ImageTesting