cartopy
cartopy copied to clipboard
Y-axis not always limited when plotting curvilinear data.
Description
Hi Cartopy 🙂
Depending on what central_longitude is used, a PlateCarree plot of curvilinear data may either have a Y-axis correctly limited to the extent of the data, or incorrectly set to the global -90 +90 limits.
Expected behaviour: y-axis is always limited to the extent of the data being plotted.
I have seen this with both Shapely v1.8.5 and v2.0.0, but interestingly the central_longitudes that produce each behaviour are different between Shapely versions.

Code to reproduce
"""
Script to demonstrate inconsistent global vs limited y-axis when plotting
curvilinear data to PlateCarree, dependent on central_longitude.
"""
from tempfile import NamedTemporaryFile
from matplotlib import pyplot as plt
import numpy as np
import requests
from scipy.io import netcdf_file
from cartopy.crs import PlateCarree
def _get_2d_coord_bound_grid(bounds):
bounds_shape = bounds.shape
result = np.zeros((bounds_shape[0] + 1, bounds_shape[1] + 1))
result[:-1, :-1] = bounds[:, :, 0]
result[:-1, -1] = bounds[:, -1, 1]
result[-1, :-1] = bounds[-1, :, 3]
result[-1, -1] = bounds[-1, -1, 2]
return result
def main():
my_request = requests.get(
"https://github.com/SciTools/iris-test-data/raw/353d15575f6419db1b0d23a1a203ac95a94daf14/test_data/NetCDF/ORCA2/votemper.nc"
)
with NamedTemporaryFile(suffix=".nc") as file_write:
file_write.write(my_request.content)
my_nc = netcdf_file(file_write.name, maskandscale=True)
x = _get_2d_coord_bound_grid(my_nc.variables["lont_bounds"])
y = _get_2d_coord_bound_grid(my_nc.variables["latt_bounds"])
data = my_nc.variables["votemper"][0, 0]
central_lons = [173, 174, -64, -63]
subplot_shape = (len(central_lons) * 100) + 10
plt.figure(figsize=[5, 2.5 * len(central_lons)])
for ix, c_lon in enumerate(central_lons, start=1):
ax = plt.subplot(subplot_shape + ix, projection=PlateCarree(central_longitude=c_lon))
ax.pcolormesh(x, y, data, transform=PlateCarree())
ax.coastlines()
ax.text(-210, 0, f"central lon: {c_lon}", rotation="vertical", verticalalignment="center")
plt.show()
if __name__ == "__main__":
main()
Full environment definition
Operating system
Linux RHEL7
Cartopy version
v0.21.1
conda list
# Name Version Build Channel
_libgcc_mutex 0.1 conda_forge conda-forge
_openmp_mutex 4.5 2_gnu conda-forge
blas 1.0 openblas conda-main
brotli 1.0.9 h5eee18b_7 conda-main
brotli-bin 1.0.9 h5eee18b_7 conda-main
brotlipy 0.7.0 py310h7f8727e_1002 conda-main
bzip2 1.0.8 h7b6447c_0 conda-main
c-ares 1.18.1 h7f8727e_0 conda-main
ca-certificates 2022.10.11 h06a4308_0 conda-main
cartopy 0.21.1 py310hcb7e713_0 conda-forge
certifi 2022.12.7 py310h06a4308_0 conda-main
cffi 1.15.1 py310h5eee18b_3 conda-main
charset-normalizer 2.0.4 pyhd3eb1b0_0 conda-main
contourpy 1.0.5 py310hdb19cb5_0 conda-main
cryptography 38.0.1 py310h9ce1e76_0 conda-main
cycler 0.11.0 pyhd3eb1b0_0 conda-main
fftw 3.3.9 h27cfd23_1 conda-main
fonttools 4.25.0 pyhd3eb1b0_0 conda-main
freetype 2.12.1 h4a9f257_0 conda-main
geos 3.11.1 h27087fc_0 conda-forge
giflib 5.2.1 h7b6447c_0 conda-main
idna 3.4 py310h06a4308_0 conda-main
jpeg 9e h7f8727e_0 conda-main
kiwisolver 1.4.4 py310h6a678d5_0 conda-main
krb5 1.19.2 hac12032_0 conda-main
lcms2 2.12 h3be6417_0 conda-main
ld_impl_linux-64 2.38 h1181459_1 conda-main
lerc 3.0 h295c915_0 conda-main
libbrotlicommon 1.0.9 h5eee18b_7 conda-main
libbrotlidec 1.0.9 h5eee18b_7 conda-main
libbrotlienc 1.0.9 h5eee18b_7 conda-main
libcurl 7.86.0 h91b91d3_0 conda-main
libdeflate 1.8 h7f8727e_5 conda-main
libedit 3.1.20221030 h5eee18b_0 conda-main
libev 4.33 h7f8727e_1 conda-main
libffi 3.4.2 h6a678d5_6 conda-main
libgcc-ng 12.2.0 h65d4601_19 conda-forge
libgfortran-ng 11.2.0 h00389a5_1 conda-main
libgfortran5 11.2.0 h1234567_1 conda-main
libgomp 12.2.0 h65d4601_19 conda-forge
libnghttp2 1.46.0 hce63b2e_0 conda-main
libopenblas 0.3.21 h043d6bf_0 conda-main
libpng 1.6.37 hbc83047_0 conda-main
libssh2 1.10.0 h8f2d780_0 conda-main
libstdcxx-ng 12.2.0 h46fd767_19 conda-forge
libtiff 4.4.0 hecacb30_2 conda-main
libuuid 1.41.5 h5eee18b_0 conda-main
libwebp 1.2.4 h11a3e52_0 conda-main
libwebp-base 1.2.4 h5eee18b_0 conda-main
lz4-c 1.9.4 h6a678d5_0 conda-main
matplotlib-base 3.6.2 py310h945d387_0 conda-main
munkres 1.1.4 py_0 conda-main
ncurses 6.3 h5eee18b_3 conda-main
numpy 1.23.5 py310hac523dd_0 conda-main
numpy-base 1.23.5 py310h375b286_0 conda-main
openssl 1.1.1s h7f8727e_0 conda-main
packaging 22.0 py310h06a4308_0 conda-main
pillow 9.3.0 py310hace64e9_1 conda-main
pip 22.3.1 py310h06a4308_0 conda-main
proj 7.2.0 h1217e81_1 conda-main
pycparser 2.21 pyhd3eb1b0_0 conda-main
pyopenssl 22.0.0 pyhd3eb1b0_0 conda-main
pyparsing 3.0.9 py310h06a4308_0 conda-main
pyproj 3.1.0 py310he0da13a_4 conda-main
pyshp 2.1.3 pyhd3eb1b0_0 conda-main
pysocks 1.7.1 py310h06a4308_0 conda-main
python 3.10.8 h7a1cb2a_1 conda-main
python-dateutil 2.8.2 pyhd3eb1b0_0 conda-main
python_abi 3.10 2_cp310 conda-forge
readline 8.2 h5eee18b_0 conda-main
requests 2.28.1 py310h06a4308_0 conda-main
scipy 1.9.3 py310heeff2f4_0 conda-main
setuptools 65.5.0 py310h06a4308_0 conda-main
shapely 2.0.0 py310h8b84c32_0 conda-forge
six 1.16.0 pyhd3eb1b0_1 conda-main
sqlite 3.40.0 h5082296_0 conda-main
tk 8.6.12 h1ccaba5_0 conda-main
tzdata 2022g h04d1e81_0 conda-main
urllib3 1.26.13 py310h06a4308_0 conda-main
wheel 0.37.1 pyhd3eb1b0_0 conda-main
xz 5.2.8 h5eee18b_0 conda-main
zlib 1.2.13 h5eee18b_0 conda-main
zstd 1.5.2 ha4553b6_0 conda-main
pip list
Package Version
------------------ ---------
brotlipy 0.7.0
Cartopy 0.21.1
certifi 2022.12.7
cffi 1.15.1
charset-normalizer 2.0.4
contourpy 1.0.5
cryptography 38.0.1
cycler 0.11.0
fonttools 4.25.0
idna 3.4
kiwisolver 1.4.4
matplotlib 3.6.2
munkres 1.1.4
numpy 1.23.5
packaging 22.0
Pillow 9.3.0
pip 22.3.1
pycparser 2.21
pyOpenSSL 22.0.0
pyparsing 3.0.9
pyproj 3.1.0
pyshp 2.1.3
PySocks 1.7.1
python-dateutil 2.8.2
requests 2.28.1
scipy 1.9.3
setuptools 65.5.0
shapely 2.0.0
six 1.16.0
urllib3 1.26.13
wheel 0.37.1
My guess would be that the wrapped cells are different between the cases and there are some different autoscaling limits associated with those cells.
https://github.com/SciTools/cartopy/blob/a8f62baf12e73921a5910a61f2c7b75d8dfc07fb/lib/cartopy/mpl/geoaxes.py#L1956-L1990
When we call self.pcolor() there is an autoscale_view() at the end of that routine, and perhaps there are some Polygons that get attached to the boundary and follow it creating the full data limits. I think you can probably investigate the private mesh._wrapped_collection_fix to see what those polygons look like in each of the cases.
Thanks @greglucas. I'm unlikely to have the resource to investigate further in the near future (took me a whole day to get this far!), but it's great to have the ideas here for when anyone wants to pick this up.