Skip to content

Commit 0baa5e0

Browse files
authored
Merge pull request #1892 from greglucas/pcolor-shading-nearest
FIX: pcolor shading with nearest
2 parents 78ea7f8 + f3c9315 commit 0baa5e0

File tree

3 files changed

+62
-8
lines changed

3 files changed

+62
-8
lines changed

lib/cartopy/mpl/geoaxes.py

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1723,7 +1723,7 @@ def pcolormesh(self, *args, **kwargs):
17231723
"""
17241724
# Add in an argument checker to handle Matplotlib's potential
17251725
# interpolation when coordinate wraps are involved
1726-
args = self._wrap_args(*args, **kwargs)
1726+
args, kwargs = self._wrap_args(*args, **kwargs)
17271727
result = super().pcolormesh(*args, **kwargs)
17281728
# Wrap the quadrilaterals if necessary
17291729
result = self._wrap_quadmesh(result, **kwargs)
@@ -1745,8 +1745,11 @@ def _wrap_args(self, *args, **kwargs):
17451745
if not (kwargs.get('shading', default_shading) in
17461746
('nearest', 'auto') and len(args) == 3 and
17471747
getattr(kwargs.get('transform'), '_wrappable', False)):
1748-
return args
1748+
return args, kwargs
17491749

1750+
# We have changed the shading from nearest/auto to flat
1751+
# due to the addition of an extra coordinate
1752+
kwargs['shading'] = 'flat'
17501753
X = np.asanyarray(args[0])
17511754
Y = np.asanyarray(args[1])
17521755
nrows, ncols = np.asanyarray(args[2]).shape
@@ -1782,7 +1785,7 @@ def _interp_grid(X, wrap=0):
17821785
X = _interp_grid(X.T, wrap=xwrap).T
17831786
Y = _interp_grid(Y.T).T
17841787

1785-
return (X, Y, args[2])
1788+
return (X, Y, args[2]), kwargs
17861789

17871790
def _wrap_quadmesh(self, collection, **kwargs):
17881791
"""
@@ -1798,8 +1801,13 @@ def _wrap_quadmesh(self, collection, **kwargs):
17981801
# Get the quadmesh data coordinates
17991802
coords = collection._coordinates
18001803
Ny, Nx, _ = coords.shape
1804+
if kwargs.get('shading') == 'gouraud':
1805+
# Gouraud shading has the same shape for coords and data
1806+
data_shape = Ny, Nx
1807+
else:
1808+
data_shape = Ny - 1, Nx - 1
18011809
# data array
1802-
C = collection.get_array().reshape((Ny - 1, Nx - 1))
1810+
C = collection.get_array().reshape(data_shape)
18031811

18041812
transformed_pts = self.projection.transform_points(
18051813
t, coords[..., 0], coords[..., 1])
@@ -1828,6 +1836,23 @@ def _wrap_quadmesh(self, collection, **kwargs):
18281836
# No wrapping needed
18291837
return collection
18301838

1839+
# Wrapping with gouraud shading is error-prone. We will do our best,
1840+
# but pcolor does not handle gouraud shading, so there needs to be
1841+
# another way to handle the wrapped cells.
1842+
if kwargs.get('shading') == 'gouraud':
1843+
warnings.warn("Handling wrapped coordinates with gouraud "
1844+
"shading is likely to introduce artifacts. "
1845+
"It is recommended to remove the wrap manually "
1846+
"before calling pcolormesh.")
1847+
# With gouraud shading, we actually want an (Ny, Nx) shaped mask
1848+
gmask = np.zeros(data_shape, dtype=bool)
1849+
# If any of the cells were wrapped, apply it to all 4 corners
1850+
gmask[:-1, :-1] |= mask
1851+
gmask[1:, :-1] |= mask
1852+
gmask[1:, 1:] |= mask
1853+
gmask[:-1, 1:] |= mask
1854+
mask = gmask
1855+
18311856
# We have quadrilaterals that cross the wrap boundary
18321857
# Now, we need to update the original collection with
18331858
# a mask over those cells and use pcolor to draw those
@@ -1908,7 +1933,11 @@ def pcolor(self, *args, **kwargs):
19081933
"""
19091934
# Add in an argument checker to handle Matplotlib's potential
19101935
# interpolation when coordinate wraps are involved
1911-
args = self._wrap_args(*args, **kwargs)
1936+
args, kwargs = self._wrap_args(*args, **kwargs)
1937+
if matplotlib.__version__ < "3.3":
1938+
# MPL 3.3 introduced the shading option, and it isn't
1939+
# handled before that for pcolor calls.
1940+
kwargs.pop('shading', None)
19121941
result = super().pcolor(*args, **kwargs)
19131942

19141943
# Update the datalim for this pcolor.

lib/cartopy/tests/mpl/test_mpl_integration.py

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -587,7 +587,7 @@ def test_pcolormesh_diagonal_wrap():
587587
# and the bottom edge on the other gets wrapped properly
588588
xs = [[160, 170], [190, 200]]
589589
ys = [[-10, -10], [10, 10]]
590-
zs = [[0, 1], [0, 1]]
590+
zs = [[0]]
591591

592592
ax = plt.axes(projection=ccrs.PlateCarree())
593593
mesh = ax.pcolormesh(xs, ys, zs)
@@ -659,6 +659,31 @@ def test_pcolormesh_wrap_set_array():
659659
return ax.figure
660660

661661

662+
@pytest.mark.parametrize('shading, input_size, expected', [
663+
pytest.param('auto', 3, 4, id='auto same size'),
664+
pytest.param('auto', 4, 4, id='auto input larger'),
665+
pytest.param('nearest', 3, 4, id='nearest same size'),
666+
pytest.param('nearest', 4, 4, id='nearest input larger'),
667+
pytest.param('flat', 4, 4, id='flat input larger'),
668+
pytest.param('gouraud', 3, 3, id='gouraud same size')
669+
])
670+
def test_pcolormesh_shading(shading, input_size, expected):
671+
# Testing that the coordinates are all broadcast as expected with
672+
# the various shading options
673+
# The data shape is (3, 3) and we are changing the input shape
674+
# based upon that
675+
ax = plt.axes(projection=ccrs.PlateCarree())
676+
677+
x = np.arange(input_size)
678+
y = np.arange(input_size)
679+
d = np.zeros((3, 3))
680+
681+
coll = ax.pcolormesh(x, y, d, shading=shading)
682+
# We can use coll.get_coordinates() once MPL >= 3.5 is required
683+
# For now, we use the private variable for testing
684+
assert coll._coordinates.shape == (expected, expected, 2)
685+
686+
662687
@pytest.mark.natural_earth
663688
@pytest.mark.mpl_image_compare(filename='quiver_plate_carree.png')
664689
def test_quiver_plate_carree():

lib/cartopy/tests/mpl/test_pseudo_color.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515

1616
def test_pcolormesh_partially_masked():
17-
data = np.ma.masked_all((40, 30))
17+
data = np.ma.masked_all((39, 29))
1818
data[0:100] = 10
1919

2020
# Check that a partially masked data array does trigger a pcolor call.
@@ -26,7 +26,7 @@ def test_pcolormesh_partially_masked():
2626

2727

2828
def test_pcolormesh_invisible():
29-
data = np.zeros((3, 3))
29+
data = np.zeros((2, 2))
3030

3131
# Check that a fully invisible mesh doesn't fail.
3232
with mock.patch('cartopy.mpl.geoaxes.GeoAxes.pcolor') as pcolor:

0 commit comments

Comments
 (0)