Repository: SciTools/cartopy

Issue:
Remove path_to_geos heuristics
Ultimately it'd be nicer to remove the heuristic guesswork from path_to_geos by modifying MPL to actually use the CLOSE_POLY vertex code when a path describes a polygon. In the meantime, we can get a chunk of that benefit by removing the heuristics and making GeoAxes.contourf() ensure the resulting paths use CLOSE_POLY.

(Originally raised in #63.)



Discussion:
@rhattersley my head is not in this space, but after a little reading around, I can't see anything obvious that suggests that this has been done yet?  Can you confirm this for me.
If so, I suggest we modify the milestone for this issue to 0.12.0?  Can you do the honors @rhattersley /@pelson?

> I can't see anything obvious that suggests that this has been done yet?

Me neither.

Thank you for confirming @rhattersley 


Resolving patch:
diff --git a/lib/cartopy/mpl/patch.py b/lib/cartopy/mpl/patch.py
--- a/lib/cartopy/mpl/patch.py
+++ b/lib/cartopy/mpl/patch.py
@@ -29,6 +29,7 @@
 from __future__ import (absolute_import, division, print_function)
 
 import numpy as np
+import matplotlib
 from matplotlib.path import Path
 import shapely.geometry as sgeom
 
@@ -69,6 +70,7 @@ def geos_to_path(shape):
         def poly_codes(poly):
             codes = np.ones(len(poly.xy[0])) * Path.LINETO
             codes[0] = Path.MOVETO
+            codes[-1] = Path.CLOSEPOLY
             return codes
         if shape.is_empty:
             return []
@@ -162,15 +164,16 @@ def path_to_geos(path, force_ccw=False):
         if len(path_verts) == 0:
             continue
 
-        # XXX A path can be given which does not end with close poly, in that
-        # situation, we have to guess?
         verts_same_as_first = np.all(path_verts[0, :] == path_verts[1:, :],
                                      axis=1)
         if all(verts_same_as_first):
             geom = sgeom.Point(path_verts[0, :])
         elif path_verts.shape[0] > 4 and path_codes[-1] == Path.CLOSEPOLY:
             geom = sgeom.Polygon(path_verts[:-1, :])
-        elif path_verts.shape[0] > 3 and verts_same_as_first[-1]:
+        elif (matplotlib.__version__ < '2.2.0' and
+                # XXX A path can be given which does not end with close poly,
+                # in that situation, we have to guess?
+                path_verts.shape[0] > 3 and verts_same_as_first[-1]):
             geom = sgeom.Polygon(path_verts)
         else:
             geom = sgeom.LineString(path_verts)


Test patch:
diff --git a/lib/cartopy/tests/mpl/baseline_images/mpl/test_features/gshhs_coastlines.png b/lib/cartopy/tests/mpl/baseline_images/mpl/test_features/gshhs_coastlines.png
Binary files a/lib/cartopy/tests/mpl/baseline_images/mpl/test_features/gshhs_coastlines.png and b/lib/cartopy/tests/mpl/baseline_images/mpl/test_features/gshhs_coastlines.png differ
diff --git a/lib/cartopy/tests/mpl/baseline_images/mpl/test_mpl_integration/natural_earth_interface.png b/lib/cartopy/tests/mpl/baseline_images/mpl/test_mpl_integration/natural_earth_interface.png
Binary files a/lib/cartopy/tests/mpl/baseline_images/mpl/test_mpl_integration/natural_earth_interface.png and b/lib/cartopy/tests/mpl/baseline_images/mpl/test_mpl_integration/natural_earth_interface.png differ
diff --git a/lib/cartopy/tests/mpl/test_patch.py b/lib/cartopy/tests/mpl/test_patch.py
--- a/lib/cartopy/tests/mpl/test_patch.py
+++ b/lib/cartopy/tests/mpl/test_patch.py
@@ -1,4 +1,4 @@
-# (C) British Crown Copyright 2015 - 2017, Met Office
+# (C) British Crown Copyright 2015 - 2018, Met Office
 #
 # This file is part of cartopy.
 #
@@ -17,7 +17,9 @@
 
 from __future__ import (absolute_import, division, print_function)
 
+import matplotlib
 from matplotlib.path import Path
+import pytest
 import shapely.geometry as sgeom
 
 import cartopy.mpl.patch as cpatch
@@ -33,12 +35,21 @@ def test_empty_polyon(self):
         assert [type(geom) for geom in geoms] == [sgeom.Point, sgeom.Point]
         assert len(geoms) == 2
 
+    @pytest.mark.skipif(matplotlib.__version__ < '2.2.0',
+                        reason='Paths may not be closed with old Matplotlib.')
+    def test_non_polygon_loop(self):
+        p = Path([[0, 10], [170, 20], [-170, 30], [0, 10]],
+                 codes=[1, 2, 2, 2])
+        geoms = cpatch.path_to_geos(p)
+        assert [type(geom) for geom in geoms] == [sgeom.MultiLineString]
+        assert len(geoms) == 1
+
     def test_polygon_with_interior_and_singularity(self):
         # A geometry with two interiors, one a single point.
         p = Path([[0, -90], [200, -40], [200, 40], [0, 40], [0, -90],
                   [126, 26], [126, 26], [126, 26], [126, 26], [126, 26],
                   [114, 5], [103, 8], [126, 12], [126, 0], [114, 5]],
-                 codes=[1, 2, 2, 2, 2, 1, 2, 2, 2, 2, 1, 2, 2, 2, 2])
+                 codes=[1, 2, 2, 2, 79, 1, 2, 2, 2, 79, 1, 2, 2, 2, 79])
         geoms = cpatch.path_to_geos(p)
         assert [type(geom) for geom in geoms] == [sgeom.Polygon, sgeom.Point]
         assert len(geoms[0].interiors) == 1