cartopy icon indicating copy to clipboard operation
cartopy copied to clipboard

Y-axis not always limited when plotting curvilinear data.

Open trexfeathers opened this issue 2 years ago • 2 comments

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.

cartopy_curvilinear_global

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

trexfeathers avatar Jan 06 '23 16:01 trexfeathers

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.

greglucas avatar Jan 08 '23 13:01 greglucas

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.

trexfeathers avatar Jan 09 '23 10:01 trexfeathers