Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Backport PRs for 0.20.1 #1904

Merged
merged 8 commits into from
Oct 8, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 34 additions & 5 deletions lib/cartopy/mpl/geoaxes.py
Original file line number Diff line number Diff line change
Expand Up @@ -1793,7 +1793,7 @@ def pcolormesh(self, *args, **kwargs):
"""
# Add in an argument checker to handle Matplotlib's potential
# interpolation when coordinate wraps are involved
args = self._wrap_args(*args, **kwargs)
args, kwargs = self._wrap_args(*args, **kwargs)
result = matplotlib.axes.Axes.pcolormesh(self, *args, **kwargs)
# Wrap the quadrilaterals if necessary
result = self._wrap_quadmesh(result, **kwargs)
Expand All @@ -1815,8 +1815,11 @@ def _wrap_args(self, *args, **kwargs):
if not (kwargs.get('shading', default_shading) in
('nearest', 'auto') and len(args) == 3 and
getattr(kwargs.get('transform'), '_wrappable', False)):
return args
return args, kwargs

# We have changed the shading from nearest/auto to flat
# due to the addition of an extra coordinate
kwargs['shading'] = 'flat'
X = np.asanyarray(args[0])
Y = np.asanyarray(args[1])
nrows, ncols = np.asanyarray(args[2]).shape
Expand Down Expand Up @@ -1852,7 +1855,7 @@ def _interp_grid(X, wrap=0):
X = _interp_grid(X.T, wrap=xwrap).T
Y = _interp_grid(Y.T).T

return (X, Y, args[2])
return (X, Y, args[2]), kwargs

def _wrap_quadmesh(self, collection, **kwargs):
"""
Expand All @@ -1868,8 +1871,13 @@ def _wrap_quadmesh(self, collection, **kwargs):
# Get the quadmesh data coordinates
coords = collection._coordinates
Ny, Nx, _ = coords.shape
if kwargs.get('shading') == 'gouraud':
# Gouraud shading has the same shape for coords and data
data_shape = Ny, Nx
else:
data_shape = Ny - 1, Nx - 1
# data array
C = collection.get_array().reshape((Ny - 1, Nx - 1))
C = collection.get_array().reshape(data_shape)

transformed_pts = self.projection.transform_points(
t, coords[..., 0], coords[..., 1])
Expand Down Expand Up @@ -1898,6 +1906,23 @@ def _wrap_quadmesh(self, collection, **kwargs):
# No wrapping needed
return collection

# Wrapping with gouraud shading is error-prone. We will do our best,
# but pcolor does not handle gouraud shading, so there needs to be
# another way to handle the wrapped cells.
if kwargs.get('shading') == 'gouraud':
warnings.warn("Handling wrapped coordinates with gouraud "
"shading is likely to introduce artifacts. "
"It is recommended to remove the wrap manually "
"before calling pcolormesh.")
# With gouraud shading, we actually want an (Ny, Nx) shaped mask
gmask = np.zeros(data_shape, dtype=bool)
# If any of the cells were wrapped, apply it to all 4 corners
gmask[:-1, :-1] |= mask
gmask[1:, :-1] |= mask
gmask[1:, 1:] |= mask
gmask[:-1, 1:] |= mask
mask = gmask

# We have quadrilaterals that cross the wrap boundary
# Now, we need to update the original collection with
# a mask over those cells and use pcolor to draw those
Expand Down Expand Up @@ -1978,7 +2003,11 @@ def pcolor(self, *args, **kwargs):
"""
# Add in an argument checker to handle Matplotlib's potential
# interpolation when coordinate wraps are involved
args = self._wrap_args(*args, **kwargs)
args, kwargs = self._wrap_args(*args, **kwargs)
if matplotlib.__version__ < "3.3":
# MPL 3.3 introduced the shading option, and it isn't
# handled before that for pcolor calls.
kwargs.pop('shading', None)
result = matplotlib.axes.Axes.pcolor(self, *args, **kwargs)

# Update the datalim for this pcolor.
Expand Down
4 changes: 1 addition & 3 deletions lib/cartopy/mpl/gridliner.py
Original file line number Diff line number Diff line change
Expand Up @@ -796,8 +796,6 @@ def update_artist(artist, renderer):
# Cache a few things so they aren't re-calculated in the loops.
crs_transform = self._crs_transform().transform
inverse_data_transform = self.axes.transData.inverted().transform_point
if self.x_inline or self.y_inline:
pc_transform = PlateCarree()

for xylabel, lines, line_ticks, formatter, label_style in (
('x', lon_lines, lon_ticks,
Expand Down Expand Up @@ -899,7 +897,7 @@ def update_artist(artist, renderer):
# Initial text specs
x0, y0 = pt0
if x_inline or y_inline:
kw = {'rotation': 0, 'transform': pc_transform,
kw = {'rotation': 0, 'transform': self.crs,
'ha': 'center', 'va': 'center'}
loc = 'inline'
else:
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified lib/cartopy/tests/mpl/baseline_images/mpl/test_ticks/xyticks.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
33 changes: 30 additions & 3 deletions lib/cartopy/tests/mpl/test_mpl_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -583,7 +583,7 @@ def test_pcolormesh_diagonal_wrap():
# and the bottom edge on the other gets wrapped properly
xs = [[160, 170], [190, 200]]
ys = [[-10, -10], [10, 10]]
zs = [[0, 1], [0, 1]]
zs = [[0]]

ax = plt.axes(projection=ccrs.PlateCarree())
mesh = ax.pcolormesh(xs, ys, zs)
Expand Down Expand Up @@ -652,6 +652,31 @@ def test_pcolormesh_wrap_set_array():
coll.set_array(Z.ravel())


@pytest.mark.parametrize('shading, input_size, expected', [
pytest.param('auto', 3, 4, id='auto same size'),
pytest.param('auto', 4, 4, id='auto input larger'),
pytest.param('nearest', 3, 4, id='nearest same size'),
pytest.param('nearest', 4, 4, id='nearest input larger'),
pytest.param('flat', 4, 4, id='flat input larger'),
pytest.param('gouraud', 3, 3, id='gouraud same size')
])
def test_pcolormesh_shading(shading, input_size, expected):
# Testing that the coordinates are all broadcast as expected with
# the various shading options
# The data shape is (3, 3) and we are changing the input shape
# based upon that
ax = plt.axes(projection=ccrs.PlateCarree())

x = np.arange(input_size)
y = np.arange(input_size)
d = np.zeros((3, 3))

coll = ax.pcolormesh(x, y, d, shading=shading)
# We can use coll.get_coordinates() once MPL >= 3.5 is required
# For now, we use the private variable for testing
assert coll._coordinates.shape == (expected, expected, 2)


@pytest.mark.natural_earth
@ImageTesting(['quiver_plate_carree'])
def test_quiver_plate_carree():
Expand Down Expand Up @@ -830,8 +855,10 @@ def test_barbs_1d_transformed():


@pytest.mark.natural_earth
@ImageTesting(['streamplot'], style='mpl20',
tolerance=42 if MPL_VERSION < parse_version('3.2') else 0.54)
@ImageTesting(
['streamplot'], style='mpl20',
tolerance=(42 if MPL_VERSION.release[:2] < (3, 2) else
9.77 if MPL_VERSION.release[:2] < (3, 5) else 0.5))
def test_streamplot():
x = np.arange(-60, 42.5, 2.5)
y = np.arange(30, 72.5, 2.5)
Expand Down
4 changes: 2 additions & 2 deletions lib/cartopy/tests/mpl/test_pseudo_color.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@


def test_pcolormesh_partially_masked():
data = np.ma.masked_all((40, 30))
data = np.ma.masked_all((39, 29))
data[0:100] = 10

# Check that a partially masked data array does trigger a pcolor call.
Expand All @@ -27,7 +27,7 @@ def test_pcolormesh_partially_masked():


def test_pcolormesh_invisible():
data = np.zeros((3, 3))
data = np.zeros((2, 2))

# Check that a fully invisible mesh doesn't fail.
with mock.patch('cartopy.mpl.geoaxes.GeoAxes.pcolor') as pcolor:
Expand Down
33 changes: 12 additions & 21 deletions lib/cartopy/tests/mpl/test_ticks.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,8 @@
from cartopy.tests.mpl import ImageTesting


ticks_tolerance = 7


@pytest.mark.natural_earth
@ImageTesting(['xticks_no_transform'],
tolerance=ticks_tolerance)
@ImageTesting(['xticks_no_transform'])
def test_set_xticks_no_transform():
ax = plt.axes(projection=ccrs.PlateCarree())
ax.coastlines('110m')
Expand All @@ -26,12 +22,9 @@ def test_set_xticks_no_transform():


@pytest.mark.natural_earth
@ImageTesting(['xticks_cylindrical'],
tolerance=ticks_tolerance)
@ImageTesting(['xticks_cylindrical'])
def test_set_xticks_cylindrical():
ax = plt.axes(projection=ccrs.Mercator(
min_latitude=-85.,
max_latitude=85.))
ax = plt.axes(projection=ccrs.Mercator(min_latitude=-85, max_latitude=85))
ax.coastlines('110m')
ax.xaxis.set_major_formatter(LongitudeFormatter(degree_symbol=''))
ax.set_xticks([-180, -90, 0, 90, 180], crs=ccrs.PlateCarree())
Expand All @@ -48,40 +41,38 @@ def test_set_xticks_non_cylindrical():


@pytest.mark.natural_earth
@ImageTesting(['yticks_no_transform'],
tolerance=ticks_tolerance)
@ImageTesting(['yticks_no_transform'])
def test_set_yticks_no_transform():
ax = plt.axes(projection=ccrs.PlateCarree())
ax.coastlines('110m')
ax.yaxis.set_major_formatter(LatitudeFormatter(degree_symbol=''))
ax.set_yticks([-60, -30, 0, 30, 60])
ax.set_yticks([-75, -45, 15, 45, 75], minor=True)
ax.set_yticks([-75, -45, -15, 15, 45, 75], minor=True)


@pytest.mark.natural_earth
@ImageTesting(['yticks_cylindrical'],
tolerance=ticks_tolerance)
@ImageTesting(['yticks_cylindrical'])
def test_set_yticks_cylindrical():
ax = plt.axes(projection=ccrs.Mercator(
min_latitude=-85.,
max_latitude=85.))
ax = plt.axes(projection=ccrs.Mercator(min_latitude=-85, max_latitude=85))
ax.coastlines('110m')
ax.yaxis.set_major_formatter(LatitudeFormatter(degree_symbol=''))
ax.set_yticks([-60, -30, 0, 30, 60], crs=ccrs.PlateCarree())
ax.set_yticks([-75, -45, 15, 45, 75], minor=True, crs=ccrs.PlateCarree())
ax.set_yticks([-75, -45, -15, 15, 45, 75], minor=True,
crs=ccrs.PlateCarree())


def test_set_yticks_non_cylindrical():
ax = plt.axes(projection=ccrs.Orthographic())
with pytest.raises(RuntimeError):
ax.set_yticks([-60, -30, 0, 30, 60], crs=ccrs.Geodetic())
with pytest.raises(RuntimeError):
ax.set_yticks([-75, -45, 15, 45, 75], minor=True, crs=ccrs.Geodetic())
ax.set_yticks([-75, -45, -15, 15, 45, 75], minor=True,
crs=ccrs.Geodetic())
plt.close()


@pytest.mark.natural_earth
@ImageTesting(['xyticks'], tolerance=ticks_tolerance)
@ImageTesting(['xyticks'])
def test_set_xyticks():
fig = plt.figure(figsize=(10, 10))
projections = (ccrs.PlateCarree(),
Expand Down
26 changes: 20 additions & 6 deletions lib/cartopy/tests/test_crs.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

import copy
from io import BytesIO
import os
from pathlib import Path
import pickle

import numpy as np
Expand Down Expand Up @@ -46,19 +48,31 @@ def test_osni(self, approx):
3)

def _check_osgb(self, osgb):
precision = 1

if os.environ.get('PROJ_NETWORK') != 'ON':
grid_name = 'uk_os_OSTN15_NTv2_OSGBtoETRS.tif'
available = (
Path(pyproj.datadir.get_data_dir(), grid_name).exists() or
Path(pyproj.datadir.get_user_data_dir(), grid_name).exists()
)
if not available:
import warnings
warnings.warn(f'{grid_name} is unavailable; '
'testing OSGB at reduced precision')
precision = -1

ll = ccrs.Geodetic()

# results obtained by streetmap.co.uk.
lat, lon = np.array([50.462023, -3.478831], dtype=np.double)
east, north = np.array([295132.1, 63512.6], dtype=np.double)

# note the handling of precision here...
assert_arr_almost_eq(np.array(osgb.transform_point(lon, lat, ll)),
np.array([east, north]),
1)
assert_arr_almost_eq(ll.transform_point(east, north, osgb),
[lon, lat],
2)
assert_almost_equal(osgb.transform_point(lon, lat, ll), [east, north],
decimal=precision)
assert_almost_equal(ll.transform_point(east, north, osgb), [lon, lat],
decimal=2)

r_lon, r_lat = ll.transform_point(east, north, osgb)
r_inverted = np.array(osgb.transform_point(r_lon, r_lat, ll))
Expand Down
6 changes: 3 additions & 3 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,11 @@

import fnmatch
import os
import shutil

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

E402 module level import not at top of file

import subprocess
import warnings
from collections import defaultdict
from distutils.spawn import find_executable
from distutils.sysconfig import get_config_var
from sysconfig import get_config_var

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

E402 module level import not at top of file


from setuptools import Command, Extension, convert_path, setup

Expand Down Expand Up @@ -142,7 +142,7 @@ def find_package_tree(root_path, root_package):

# Proj
def find_proj_version_by_program(conda=None):
proj = find_executable('proj')
proj = shutil.which('proj')
if proj is None:
print(
'Proj {} must be installed.'.format(
Expand Down
1 change: 1 addition & 0 deletions tools/cartopy_feature_download.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
('cultural', 'admin_0_pacific_groupings', '110m'),
('cultural', 'admin_1_states_provinces', '110m'),
('cultural', 'admin_1_states_provinces_lines', '110m'),
('cultural', 'admin_1_states_provinces_lakes', ALL_SCALES),
),
}

Expand Down