Skip to content

GEOSException in uxarr.plot(..., projection=cartopy.crs.Robinson(central_longitude=...)) #1795

Description

@Sevans711

Version

v2026.09.1

How did you install UXarray?

Source

What happened?

Using almost any cartopy projection with a nonzero central_longitude causes uxarr.plot(..., projection=that_projection) to crash with GEOSException.

(note: this is the same bug which @erogluorhan encountered during the recent usclivar meeting.)

The exception seems to be occurring inside uxarray's to_geodataframe, so it is likely something that should be fixed within uxarray, not an upstream issue. The full traceback looks like this:

click to show full traceback
---------------------------------------------------------------------------
GEOSException                             Traceback (most recent call last)
Cell In[113], line 1
----> 1 plot_obj = arr.plot(projection=ccrs.Robinson(central_longitude=100))

File ~/Code/uxarray/uxarray/plot/accessor.py:385, in UxDataArrayPlotAccessor.__call__(self, **kwargs)
    382 def __call__(self, **kwargs) -> Any:
    383     if self._uxda._face_centered():
    384         # polygons for face-centered data
--> 385         return self.polygons(**kwargs)
    386     else:
    387         # points for node and edge centered data
    388         return self.points(**kwargs)

File ~/Code/uxarray/uxarray/plot/accessor.py:473, in UxDataArrayPlotAccessor.polygons(self, periodic_elements, backend, engine, rasterize, dynamic, projection, xlabel, ylabel, *args, **kwargs)
    470 if "clabel" not in kwargs and self._uxda.name is not None:
    471     kwargs["clabel"] = self._uxda.name
--> 473 gdf = self._uxda.to_geodataframe(
    474     periodic_elements=periodic_elements,
    475     projection=kwargs.get("projection"),
    476     engine=engine,
    477     project=False,
    478 )
    480 # import datashader  # no need to import explicitly, but gets used below when rasterize=True.
    481 # (commented here for future reference, since datashader appears in uxarray dependency list,
    482 #    but it isn't imported explicitly anywhere in uxarray. For more details see PR #1548.)
    483 return gdf.hvplot.polygons(
    484     c=self._uxda.name if self._uxda.name is not None else "var",
    485     rasterize=rasterize,
   (...)    490     **kwargs,
    491 )

File ~/Code/uxarray/uxarray/core/dataarray.py:277, in UxDataArray.to_geodataframe(self, periodic_elements, projection, cache, override, engine, exclude_antimeridian, **kwargs)
    271     raise DimensionError(
    272         f"Data Variable must be 1-dimensional, with shape {self.uxgrid.n_face} "
    273         f"for face-centered data."
    274     )
    276 if self._face_centered():
--> 277     gdf, non_nan_polygon_indices = self.uxgrid.to_geodataframe(
    278         periodic_elements=periodic_elements,
    279         projection=projection,
    280         project=kwargs.get("project", True),
    281         cache=cache,
    282         override=override,
    283         exclude_antimeridian=exclude_antimeridian,
    284         return_non_nan_polygon_indices=True,
    285         engine=engine,
    286     )
    288     if exclude_antimeridian is not None:
    289         if exclude_antimeridian:

File ~/Code/uxarray/uxarray/grid/grid.py:2608, in Grid.to_geodataframe(self, periodic_elements, projection, cache, override, engine, exclude_antimeridian, return_non_nan_polygon_indices, exclude_nan_polygons, **kwargs)
   2605         return self._gdf_cached_parameters["gdf"]
   2607 # construct a GeoDataFrame with the faces stored as polygons as the geometry
-> 2608 gdf, non_nan_polygon_indices = _grid_to_polygon_geodataframe(
   2609     self, periodic_elements, projection, project, engine
   2610 )
   2612 if exclude_nan_polygons and non_nan_polygon_indices is not None:
   2613     # exclude any polygons that contain NaN values
   2614     gdf = GeoDataFrame({"geometry": gdf["geometry"][non_nan_polygon_indices]})

File ~/Code/uxarray/uxarray/grid/geometry.py:244, in _grid_to_polygon_geodataframe(grid, periodic_elements, projection, project, engine)
    241 grid._gdf_cached_parameters["antimeridian_face_indices"] = antimeridian_face_indices
    243 if periodic_elements == "split":
--> 244     gdf = _build_geodataframe_with_antimeridian(
    245         polygon_shells,
    246         projected_polygon_shells,
    247         antimeridian_face_indices,
    248         engine=engine,
    249     )
    250 elif periodic_elements == "ignore":
    251     if engine == "geopandas":
    252         # create a geopandas.GeoDataFrame

File ~/Code/uxarray/uxarray/grid/geometry.py:325, in _build_geodataframe_with_antimeridian(polygon_shells, projected_polygon_shells, antimeridian_face_indices, engine)
    322 import spatialpandas
    323 from spatialpandas.geometry import MultiPolygonArray
--> 325 polygons = _build_corrected_shapely_polygons(
    326     polygon_shells, projected_polygon_shells, antimeridian_face_indices
    327 )
    328 if engine == "geopandas":
    329     # Create a geopandas.GeoDataFrame
    330     gdf = geopandas.GeoDataFrame({"geometry": polygons})

File ~/Code/uxarray/uxarray/grid/geometry.py:354, in _build_corrected_shapely_polygons(polygon_shells, projected_polygon_shells, antimeridian_face_indices)
    351     shells = polygon_shells
    353 # list of shapely Polygons representing each face in our grid
--> 354 polygons = Polygons(shells)
    356 # construct antimeridian polygons
    357 antimeridian_polygons = Polygons(polygon_shells[antimeridian_face_indices])

File ~/Code/venvs/py314/lib/python3.14/site-packages/shapely/decorators.py:173, in deprecate_positional.<locals>.decorator.<locals>.wrapper(*args, **kwargs)
    171 @wraps(func)
    172 def wrapper(*args, **kwargs):
--> 173     result = func(*args, **kwargs)
    175     n = len(args)
    176     if n > warn_from:

File ~/Code/venvs/py314/lib/python3.14/site-packages/shapely/decorators.py:88, in multithreading_enabled.<locals>.wrapped(*args, **kwargs)
     86     for arr in array_args:
     87         arr.flags.writeable = False
---> 88     return func(*args, **kwargs)
     89 finally:
     90     for arr, old_flag in zip(array_args, old_flags):

File ~/Code/venvs/py314/lib/python3.14/site-packages/shapely/creation.py:417, in polygons(geometries, holes, indices, out, **kwargs)
    413 geometries = np.asarray(geometries)
    414 if not isinstance(geometries, Geometry) and np.issubdtype(
    415     geometries.dtype, np.number
    416 ):
--> 417     geometries = linearrings(geometries)
    419 if indices is not None:
    420     if holes is not None:

File ~/Code/venvs/py314/lib/python3.14/site-packages/shapely/decorators.py:173, in deprecate_positional.<locals>.decorator.<locals>.wrapper(*args, **kwargs)
    171 @wraps(func)
    172 def wrapper(*args, **kwargs):
--> 173     result = func(*args, **kwargs)
    175     n = len(args)
    176     if n > warn_from:

File ~/Code/venvs/py314/lib/python3.14/site-packages/shapely/decorators.py:88, in multithreading_enabled.<locals>.wrapped(*args, **kwargs)
     86     for arr in array_args:
     87         arr.flags.writeable = False
---> 88     return func(*args, **kwargs)
     89 finally:
     90     for arr, old_flag in zip(array_args, old_flags):

File ~/Code/venvs/py314/lib/python3.14/site-packages/shapely/creation.py:316, in linearrings(coords, y, z, indices, handle_nan, out, **kwargs)
    314     handle_nan = HandleNaN.get_value(handle_nan)
    315 if indices is None:
--> 316     return lib.linearrings(coords, np.intc(handle_nan), out=out, **kwargs)
    317 else:
    318     return simple_geometries_1d(
    319         coords, indices, GeometryType.LINEARRING, handle_nan=handle_nan, out=out
    320     )

GEOSException: IllegalArgumentException: Points of LinearRing do not form a closed linestring

I checked all other projections from cartopy (except the "local" projections 'EuroPP', 'LambertZoneII', 'OSGB', 'OSNI' which focus on only one particular region) and found that, with the exception of ObliqueMercator, all of them have this error. Here's a copy-pastable block with the full list of projections I checked:

['Aitoff', 'AlbersEqualArea', 'AzimuthalEquidistant', 'EckertI', 'EckertII', 'EckertIII', 'EckertIV', 'EckertV', 'EckertVI', 'EqualEarth', 'EquidistantConic', 'Geostationary', 'Gnomonic', 'Hammer', 'InterruptedGoodeHomolosine', 'LambertAzimuthalEqualArea', 'LambertConformal', 'LambertCylindrical', 'Mercator', 'Miller', 'Mollweide', 'NearsidePerspective', 'NorthPolarStereo', 'ObliqueMercator', 'Orthographic', 'PlateCarree', 'Robinson', 'Sinusoidal', 'SouthPolarStereo', 'Stereographic', 'TransverseMercator']

What did you expect to happen?

I expected this to work just fine! It works fine when using structured grids.

Can you provide a MCVE to repoduce the bug?

import cartopy.crs as ccrs
import uxarray as ux
import xarray as xr

# the bug, with Robinson projection as example:
arr = ux.tutorial.open_dataset("outCSne30-vortex")['psi']
plot_obj = arr.plot(projection=ccrs.Robinson(central_longitude=100))
# (no need to try rendering; the bug occurs before rendering.)

# sanity check / quick demo: the bug does not occur with structured grids:
arr_as_lonlat = arr.remap.to_rectilinear(
    lon=np.linspace(-179, 179, 100),
    lat=np.linspace(-89, 89)
)
arr_as_lonlat.hvplot.quadmesh(projection=ccrs.Robinson(central_longitude=100))
# --> does not crash; plot looks reasonable.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingvisualizationPlotting or other visualizations

    Type

    No type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions