Skip to content

Commit 5556d8a

Browse files
authored
Merge pull request #31 from Chilipp/master
fix plotting of curvilinear data with bounds
2 parents 73b8bde + 38bbacf commit 5556d8a

File tree

4 files changed

+67
-6
lines changed

4 files changed

+67
-6
lines changed

CHANGELOG.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ Fixed
1313
`#29 <https://github.com/psyplot/psy-maps/pull/29>`__).
1414
- fixed plotting of data with 3D bounds (see
1515
`#30 <https://github.com/psyplot/psy-maps/pull/30>`__)
16+
- fixed plotting of curvilinear data with 3D bounds (see
17+
`#31 <https://github.com/psyplot/psy-maps/pull/31>`__)
1618

1719
v1.3.0
1820
======

psy_maps/plotters.py

Lines changed: 40 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1679,6 +1679,10 @@ def _polycolor(self):
16791679
# However, in order to wrap the boundaries correctly, we have to
16801680
# identify the corresponding grid cells and then use the standard
16811681
# matplotlib transform.
1682+
if xb.ndim > 2:
1683+
xb = xb.reshape((-1, xb.shape[-1]))
1684+
yb = yb.reshape((-1, yb.shape[-1]))
1685+
arr = arr.reshape(-1)
16821686
if isinstance(t, wrap_proj_types) and 'lon_0' in proj.proj4_params:
16831687
# We adopt and copy some code from the methodology of cartopy
16841688
# _pcolormesh_patched method of the geoaxes. As such, we
@@ -1717,9 +1721,6 @@ def _polycolor(self):
17171721
yb_wrap = yb[mask]
17181722
xb = xb[~mask]
17191723
yb = yb[~mask]
1720-
if xb.ndim > 2:
1721-
xb = xb.reshape((-1, xb.shape[-1]))
1722-
yb = yb.reshape((-1, yb.shape[-1]))
17231724
self.logger.debug('Making plot with %i cells', arr.size)
17241725
transformed = proj.transform_points(
17251726
t, xb.ravel(), yb.ravel())[..., :2].reshape(xb.shape + (2,))
@@ -1791,6 +1792,11 @@ def update(self, value):
17911792
proj = self.ax.projection
17921793
# HACK: We remove the cells at the boundary of the map projection
17931794
xb_wrap = None
1795+
1796+
if xb.ndim > 2:
1797+
xb = xb.reshape((-1, xb.shape[-1]))
1798+
yb = yb.reshape((-1, yb.shape[-1]))
1799+
17941800
if isinstance(t, wrap_proj_types) and 'lon_0' in proj.proj4_params:
17951801
# See the :meth:`MapPlot2D._polycolor` method for a documentation
17961802
# of the steps
@@ -1822,20 +1828,48 @@ def update(self, value):
18221828
transformed = proj.transform_points(t, xb.ravel(), yb.ravel())
18231829
xb = transformed[..., 0].reshape(orig_shape)
18241830
yb = transformed[..., 1].reshape(orig_shape)
1825-
if xb.ndim > 2:
1826-
xb = xb.reshape((-1, xb.shape[-1]))
1827-
yb = yb.reshape((-1, yb.shape[-1]))
18281831
# We insert nan values in the flattened edges arrays rather than
18291832
# plotting the grid cells separately as it considerably speeds-up code
18301833
# execution.
18311834
n = len(xb)
18321835
xb = np.c_[xb, xb[:, :1], [[np.nan]] * n].ravel()
18331836
yb = np.c_[yb, yb[:, :1], [[np.nan]] * n].ravel()
1837+
18341838
if isinstance(value, dict):
18351839
self._artists = self.ax.plot(xb, yb, **value)
18361840
else:
18371841
self._artists = self.ax.plot(xb, yb, value)
1842+
18381843
if xb_wrap is not None:
1844+
# first we drop all grid cells that have any NaN in their bounds
1845+
# as we do not know, how to handle these
1846+
valid = (~np.isnan(xb_wrap).any(-1)) & (~np.isnan(yb_wrap).any(-1))
1847+
xb_wrap = xb_wrap[valid]
1848+
yb_wrap = yb_wrap[valid]
1849+
1850+
if isinstance(t, ccrs.PlateCarree):
1851+
1852+
# identify the grid cells at the boundary
1853+
xdiff2min = xb_wrap - xb_wrap.min(axis=-1, keepdims=True)
1854+
cross_world_mask = np.any(np.abs(xdiff2min) > 180, -1)
1855+
if cross_world_mask.any():
1856+
cross_world_x = xb_wrap[cross_world_mask]
1857+
cross_world_y = yb_wrap[cross_world_mask]
1858+
cross_world_diff = xdiff2min[cross_world_mask]
1859+
xdiff2max = cross_world_x - cross_world_x.max(
1860+
axis=-1, keepdims=True)
1861+
1862+
leftx = cross_world_x.copy()
1863+
leftx[cross_world_diff > 180] -= 360
1864+
1865+
rightx = cross_world_x.copy()
1866+
rightx[xdiff2max < -180] += 360
1867+
1868+
xb_wrap = np.r_[xb_wrap[~cross_world_mask], leftx, rightx]
1869+
yb_wrap = np.r_[
1870+
yb_wrap[~cross_world_mask], cross_world_y, cross_world_y
1871+
]
1872+
18391873
n = len(xb_wrap)
18401874
xb_wrap = np.c_[xb_wrap, xb_wrap[:, :1], [[np.nan]] * n].ravel()
18411875
yb_wrap = np.c_[yb_wrap, yb_wrap[:, :1], [[np.nan]] * n].ravel()

tests/curvilinear-with-bounds.nc

2.09 MB
Binary file not shown.

tests/test_plotters.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1774,5 +1774,30 @@ def test_datagrid_3D_bounds():
17741774
assert abs(ymax - ymin - 52) < 2
17751775

17761776

1777+
def test_plot_curvilinear_datagrid(tmpdir):
1778+
"""Test if the there is no datagrid plotted over land
1779+
1780+
This implicitly checks, if grid cells at the boundary are warped correctly.
1781+
The file ``'curvilinear-with-bounds.nc'`` contains a variable on a
1782+
curvilinear grid that is only defined over the ocean (derived from MPI-OM).
1783+
Within this test, we focus on a region over land far away from
1784+
the ocean (Czech Republic) where there are no grid cells. If the datagrid
1785+
is plotted correctly, it should be all white.
1786+
"""
1787+
from matplotlib.testing.compare import compare_images
1788+
fname = os.path.join(bt.test_dir, 'curvilinear-with-bounds.nc')
1789+
# make a white plot without datagrid
1790+
kws = dict(plot=None, xgrid=False, ygrid=False, map_extent='Czech Republic')
1791+
with psy.plot.mapplot(fname, **kws) as sp:
1792+
sp.export(str(tmpdir / "ref.png")) # this is all white
1793+
# now draw the datagrid, it should still be empty (as the input file only
1794+
# defines the data over the ocean)
1795+
with psy.plot.mapplot(fname, datagrid='k-', **kws) as sp:
1796+
sp.export(str(tmpdir / "test.png")) # this should be all white, too
1797+
results = compare_images(
1798+
str(tmpdir / "ref.png"), str(tmpdir / "test.png"), tol=1)
1799+
assert results is None, results
1800+
1801+
17771802
if __name__ == '__main__':
17781803
bt.RefTestProgram()

0 commit comments

Comments
 (0)