diff --git a/lib/cartopy/mpl/feature_artist.py b/lib/cartopy/mpl/feature_artist.py index 740679398..5b6f31bbe 100644 --- a/lib/cartopy/mpl/feature_artist.py +++ b/lib/cartopy/mpl/feature_artist.py @@ -15,6 +15,7 @@ import matplotlib.artist import matplotlib.collections +import matplotlib.path as mpath import numpy as np import cartopy.mpl.patch as cpatch @@ -179,15 +180,21 @@ def draw(self, renderer, *args, **kwargs): geom_key, geom) mapping = FeatureArtist._geom_key_to_path_cache.setdefault( geom_key, {}) - geom_paths = mapping.get(key) - if geom_paths is None: + geom_path = mapping.get(key) + if geom_path is None: if ax.projection != feature_crs: projected_geom = ax.projection.project_geometry( geom, feature_crs) else: projected_geom = geom + geom_paths = cpatch.geos_to_path(projected_geom) - mapping[key] = geom_paths + if not geom_paths: + continue + # The transform may have split the geometry into two paths, we only want + # one compound path. + geom_path = mpath.Path.make_compound_path(*geom_paths) + mapping[key] = geom_path if not self._styler: style = prepared_kwargs @@ -196,7 +203,7 @@ def draw(self, renderer, *args, **kwargs): style = style_merge(dict(prepared_kwargs), self._styler(geom)) style = _freeze(style) - stylised_paths.setdefault(style, []).extend(geom_paths) + stylised_paths.setdefault(style, []).append(geom_path) transform = ax.projection._as_mpl_transform(ax) diff --git a/lib/cartopy/tests/mpl/test_feature_artist.py b/lib/cartopy/tests/mpl/test_feature_artist.py index e54561bca..d6ccff315 100644 --- a/lib/cartopy/tests/mpl/test_feature_artist.py +++ b/lib/cartopy/tests/mpl/test_feature_artist.py @@ -5,6 +5,8 @@ from unittest import mock +import matplotlib.path as mpath +import matplotlib.pyplot as plt from matplotlib.transforms import IdentityTransform import numpy as np import pytest @@ -79,8 +81,8 @@ def test_feature_artist_draw(path_collection_cls, feature): fa.draw(mock.sentinel.renderer) transform = prj_crs._as_mpl_transform(fa.axes) - expected_paths = (cached_paths(geoms[0], prj_crs) + - cached_paths(geoms[1], prj_crs), ) + expected_paths = ([cached_paths(geoms[0], prj_crs), + cached_paths(geoms[1], prj_crs)], ) expected_style = {'facecolor': 'red'} args, kwargs = path_collection_cls.call_args_list[0] @@ -114,9 +116,9 @@ def styler(geom): transform = prj_crs._as_mpl_transform(fa.axes) - calls = [{'paths': (cached_paths(geoms[0], prj_crs), ), + calls = [{'paths': ([cached_paths(geoms[0], prj_crs)], ), 'style': dict(linewidth=2, **style1)}, - {'paths': (cached_paths(geoms[1], prj_crs), ), + {'paths': ([cached_paths(geoms[1], prj_crs)], ), 'style': style2_finalized}] assert path_collection_cls.call_count == 2 @@ -125,3 +127,17 @@ def styler(geom): assert expected_call['paths'] == actual_args assert transform == actual_kwargs.pop('transform') assert expected_call['style'] == actual_kwargs + + +def test_feature_artist_geom_single_path(feature): + plot_crs = ccrs.PlateCarree(central_longitude=180) + fig, ax = plt.subplots( + subplot_kw={'projection': plot_crs}) + ax.add_feature(feature) + + fig.draw_without_rendering() + + # Square gets split into two geometries across the dateline, but should still be + # plotted as one compound path to ensure style consistency. + for geom in feature.geometries(): + assert isinstance(cached_paths(geom, plot_crs), mpath.Path) diff --git a/lib/cartopy/tests/mpl/test_mpl_integration.py b/lib/cartopy/tests/mpl/test_mpl_integration.py index 37be66818..ebe096343 100644 --- a/lib/cartopy/tests/mpl/test_mpl_integration.py +++ b/lib/cartopy/tests/mpl/test_mpl_integration.py @@ -585,7 +585,7 @@ def test_pcolormesh_set_clim_with_mask(): @pytest.mark.natural_earth @pytest.mark.mpl_image_compare(filename='pcolormesh_limited_area_wrap.png', - tolerance=1.82) + tolerance=1.83) def test_pcolormesh_limited_area_wrap(): # make up some realistic data with bounds (such as data from the UM's North # Atlantic Europe model)