Description
With cartopy 0.26.0, GeoAxes.set_extent() with a longitude range of exactly 360°
returns an essentially zero-width x range on azimuthal projections
(NorthPolarStereo, SouthPolarStereo, Orthographic, LambertAzimuthalEqualArea),
depending on the central longitude. cartopy 0.25.0 returns the expected extent
(the full disk) with the same code.
import cartopy.crs as ccrs
import matplotlib.pyplot as plt
ax = plt.figure().add_subplot(projection=ccrs.NorthPolarStereo(central_longitude=180))
ax.set_extent([-180, 180, 0, 89.5], crs=ccrs.PlateCarree(central_longitude=180))
print(ax.get_xlim(), ax.get_ylim())
| version |
x range |
y range |
| 0.25.0 |
(-1.2687e7, 1.2693e7), width 2.54e7 m |
(-1.2620e7, 1.2714e7) |
| 0.26.0 |
(-1.557e-9, -6.8e-12), width 1.5e-9 m |
(5.58e4, 1.2714e7) |
Observations:
- The result depends on
central_longitude in an irregular way (same value passed
to the projection and to the PlateCarree used for crs): 0, 140, 179, 181 and
200 give the correct extent; 90, 179.9, 180, 180.1, -180, 270 and 360 collapse.
- Same collapse with
crs=ccrs.PlateCarree() and [0, 360, 0, 89.5], for any
lat_min between 0 and 60, and for SouthPolarStereo(central_longitude=180),
Orthographic(180, 90) and LambertAzimuthalEqualArea(180, 90).
- A longitude span slightly smaller than 360° works:
[-179.999, 179.999, 0, 89.5],
[0, 359.999, 0, 89.5] or [-180, 179.9, 0, 89.5] all give width 2.54e7 m.
Consequences: since GeoAxes uses adjustable="box", the axes box also becomes
zero-width and the map is drawn as a vertical line, i.e. it disappears from the
figure. If gridlines are added, Gridliner._draw_gridliner inverts a singular
transform and savefig raises numpy.linalg.LinAlgError: Singular matrix for
some dpi values (150, 300 and 600 fail; 100, 200, 250 and 400 happen to pass,
but the map is still missing).
This looks like a side effect of the PlateCarree rework for PROJ 9.8 (#2653,
eqc + lon_0 replaced by latlong + pm): the two edges of the extent
rectangle at ±180° end up on the same meridian and the projected rectangle
degenerates to a line.
Code to reproduce
import io
import numpy as np
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
lon = np.arange(0.0, 360.0, 2.5)
lat = np.arange(-90.0, 90.1, 2.5)
LON, LAT = np.meshgrid(lon, lat)
Z = 250.0 + 40.0 * np.cos(np.radians(LAT)) + 5.0 * np.cos(np.radians(LON))
fig = plt.figure(figsize=(6.4, 4.8))
ax = fig.add_subplot(1, 1, 1, projection=ccrs.NorthPolarStereo(central_longitude=180.0))
ax.set_extent([-180.0, 180.0, 0.0, 89.5], crs=ccrs.PlateCarree(central_longitude=180.0))
print("xlim:", ax.get_xlim(), "ylim:", ax.get_ylim()) # x width ~1e-9 on 0.26.0
cf = ax.contourf(lon, lat, Z, levels=21, transform=ccrs.PlateCarree())
fig.colorbar(cf, ax=ax)
ax.coastlines()
ax.gridlines()
fig.savefig(io.BytesIO(), format="png", dpi=300) # LinAlgError on 0.26.0
Traceback
xlim: (np.float64(-1.5569669666696745e-09), np.float64(-6.8393253762197455e-12)) ylim: (np.float64(55847.329866713815), np.float64(12713600.098850505))
Traceback (most recent call last):
File "repro.py", line 15, in <module>
fig.savefig(io.BytesIO(), format="png", dpi=300)
File "matplotlib/figure.py", line 3490, in savefig
self.canvas.print_figure(fname, **kwargs)
File "matplotlib/backend_bases.py", line 2186, in print_figure
result = print_method(
File "matplotlib/backends/backend_agg.py", line 481, in print_png
self._print_pil(filename_or_obj, "png", pil_kwargs, metadata)
File "matplotlib/backends/backend_agg.py", line 429, in _print_pil
FigureCanvasAgg.draw(self)
File "matplotlib/backends/backend_agg.py", line 382, in draw
self.figure.draw(self.renderer)
File "matplotlib/figure.py", line 3257, in draw
mimage._draw_list_compositing_images(
File "matplotlib/image.py", line 134, in _draw_list_compositing_images
a.draw(renderer)
File "cartopy/mpl/geoaxes.py", line 520, in draw
return super().draw(renderer=renderer, **kwargs)
File "matplotlib/axes/_base.py", line 3226, in draw
mimage._draw_list_compositing_images(
File "matplotlib/image.py", line 134, in _draw_list_compositing_images
a.draw(renderer)
File "cartopy/mpl/gridliner.py", line 1227, in draw
self._draw_gridliner(renderer=renderer)
File "cartopy/mpl/gridliner.py", line 575, in _draw_gridliner
lon_lim, lat_lim = self._axes_domain(nx=nx, ny=ny)
File "cartopy/mpl/gridliner.py", line 1146, in _axes_domain
ax_to_bkg_patch = self.axes.transAxes - self.axes.patch.get_transform()
File "matplotlib/transforms.py", line 1463, in __sub__
return self + other.inverted()
File "matplotlib/transforms.py", line 2443, in inverted
self._b.inverted(), self._a.inverted())
File "matplotlib/transforms.py", line 2443, in inverted
self._b.inverted(), self._a.inverted())
File "matplotlib/transforms.py", line 1877, in inverted
self._inverted = Affine2D(inv(mtx), shorthand_name=shorthand_name)
File "numpy/linalg/_linalg.py", line 648, in inv
ainv = _umath_linalg.inv(a, signature=signature)
File "numpy/linalg/_linalg.py", line 145, in _raise_linalgerror_singular
raise LinAlgError("Singular matrix")
numpy.linalg.LinAlgError: Singular matrix
With cartopy 0.25.0 the same script prints
xlim: (-12686968.2, 12693088.5) ylim: (-12619811.3, 12713600.1) and saves the
figure at every dpi.
Full environment definition
Operating system
macOS 15.7.5 (arm64)
Cartopy version
0.26.0 (conda-forge build np2py312h3775662_0); 0.25.0 works
conda list (relevant packages, both from conda-forge)
Failing environment:
cartopy 0.26.0 np2py312h3775662_0
geos 3.14.1 ha834cd8_0
matplotlib-base 3.10.8 py312h605b88b_0
numpy 2.4.3 py312h84a4f5f_0
proj 9.9.0 h51dee2d_0
pyproj 3.8.0 py312h16d7083_2
pyshp 3.1.6 pyhcf101f3_0
python 3.12.14 hd05a0c4_3_cpython
shapely 2.1.2 py312h0fc4a54_3
Working environment (only cartopy, proj and pyproj differ):
cartopy 0.25.0 py312h5978115_1
geos 3.14.1 h5afe852_0
matplotlib-base 3.10.8 py312h605b88b_0
numpy 2.4.3 py312h84a4f5f_0
proj 9.7.1 hfb14a63_3
pyproj 3.7.2 py312hce17ad6_3
python 3.12.13 h8561d8f_0_cpython
shapely 2.1.2 py312h35cd81b_2
pip list
Not used (conda-forge environment).

Description
With cartopy 0.26.0,
GeoAxes.set_extent()with a longitude range of exactly 360°returns an essentially zero-width x range on azimuthal projections
(NorthPolarStereo, SouthPolarStereo, Orthographic, LambertAzimuthalEqualArea),
depending on the central longitude. cartopy 0.25.0 returns the expected extent
(the full disk) with the same code.
Observations:
central_longitudein an irregular way (same value passedto the projection and to the
PlateCarreeused forcrs): 0, 140, 179, 181 and200 give the correct extent; 90, 179.9, 180, 180.1, -180, 270 and 360 collapse.
crs=ccrs.PlateCarree()and[0, 360, 0, 89.5], for anylat_minbetween 0 and 60, and forSouthPolarStereo(central_longitude=180),Orthographic(180, 90)andLambertAzimuthalEqualArea(180, 90).[-179.999, 179.999, 0, 89.5],[0, 359.999, 0, 89.5]or[-180, 179.9, 0, 89.5]all give width 2.54e7 m.Consequences: since GeoAxes uses
adjustable="box", the axes box also becomeszero-width and the map is drawn as a vertical line, i.e. it disappears from the
figure. If gridlines are added,
Gridliner._draw_gridlinerinverts a singulartransform and
savefigraisesnumpy.linalg.LinAlgError: Singular matrixforsome dpi values (150, 300 and 600 fail; 100, 200, 250 and 400 happen to pass,
but the map is still missing).
This looks like a side effect of the PlateCarree rework for PROJ 9.8 (#2653,
eqc+lon_0replaced bylatlong+pm): the two edges of the extentrectangle at ±180° end up on the same meridian and the projected rectangle
degenerates to a line.
Code to reproduce
Traceback
With cartopy 0.25.0 the same script prints
xlim: (-12686968.2, 12693088.5) ylim: (-12619811.3, 12713600.1)and saves thefigure at every dpi.
Full environment definition
Operating system
macOS 15.7.5 (arm64)
Cartopy version
0.26.0 (conda-forge build
np2py312h3775662_0); 0.25.0 worksconda list (relevant packages, both from conda-forge)
Failing environment:
Working environment (only cartopy, proj and pyproj differ):
pip list
Not used (conda-forge environment).