Skip to content

render_shapes with datashader fails with KeyError: 'global' on non-"global" coordinate systems with geopandas 1.2 #781

Description

@lazappi

With geopandas 1.2.0, render_shapes() fails with KeyError: 'global' when the element has more than 10,000 shapes (so the datashader method is used) and the element is not in the "global" coordinate system.
The same code works with geopandas 1.1.4.

Minimal example

import geopandas as gpd
import matplotlib.pyplot as plt
import numpy as np
import shapely
import spatialdata as sd
import spatialdata_plot  # noqa: F401
from spatialdata.models import ShapesModel
from spatialdata.transformations import Identity

# More than 10,000 shapes so that render_shapes picks the datashader method
rng = np.random.default_rng(0)
xy = rng.uniform(0, 1000, size=(12_000, 2))
shapes = gpd.GeoDataFrame(geometry=[shapely.box(x, y, x + 2, y + 2) for x, y in xy])

# The element is only in a coordinate system that is not "global"
sdata = sd.SpatialData(
    shapes={"squares": ShapesModel.parse(shapes, transformations={"sample": Identity()})}
)

fig, ax = plt.subplots()
sdata.pl.render_shapes("squares").pl.show(ax=ax, coordinate_systems="sample")

With geopandas==1.2.0:

  File ".../spatialdata_plot/pl/render.py", line 910, in _render_shapes
    plot_width, plot_height, x_ext, y_ext, factor = _get_extent_and_range_for_datashader_canvas(
        transformed_element, "global", fig_params
    )
  File ".../spatialdata_plot/pl/_datashader.py", line 830, in _get_extent_and_range_for_datashader_canvas
    extent = _fast_extent(spatial_element, coordinate_system)
  File ".../spatialdata_plot/pl/utils.py", line 1678, in _fast_extent
    return _element_extent_fast(element, coordinate_system) or get_extent(element, coordinate_system=coordinate_system)
  File ".../spatialdata_plot/pl/utils.py", line 1585, in _element_extent_fast
    matrix = transformations[coordinate_system].to_affine_matrix(("x", "y"), ("x", "y"))
KeyError: 'global'

With geopandas<1.2 (1.1.4) the plot renders correctly.
With method="matplotlib" it also renders correctly.

Cause

In the datashader path of _render_shapes, the geometry is transformed into the target coordinate system and a new element is made:

transformed_element = ShapesModel.parse(
    gpd.GeoDataFrame(
        data=shapes.drop("geometry", axis=1),
        geometry=transformed_geometry,
    )
)

The extent is then computed in a hard-coded "global" coordinate system.
This relies on ShapesModel.parse adding the default {"global": Identity()} transformation.

shapes.drop(...) keeps DataFrame.attrs, which hold the original transform.
geopandas 1.1.4 dropped these attrs in the GeoDataFrame constructor, but geopandas 1.2.0 keeps them:

import geopandas as gpd, shapely

g = gpd.GeoDataFrame({"a": [1]}, geometry=[shapely.Point(0, 0)])
g.attrs["transform"] = {"sample": 1}
print(gpd.GeoDataFrame(data=g.drop("geometry", axis=1), geometry=g["geometry"]).attrs)
# geopandas 1.1.4: {}
# geopandas 1.2.0: {'transform': {'sample': 1}}

So with geopandas 1.2.0 ShapesModel.parse keeps the original transformations (here only "sample"), no "global" transformation is added, and the lookup fails.

Possible fix

The geometry is already in the target coordinate system, so the copied transform attrs can be removed before parsing.
Just passing transformations={"global": Identity()} to ShapesModel.parse is not sufficient. With geopandas 1.2.0 it raises ValueError: Transformations are both specified for the element and also passed as an argument to the parser.

This change makes the example above work with both geopandas 1.1.4 and 1.2.0:

-        transformed_element = ShapesModel.parse(
-            gpd.GeoDataFrame(
-                data=shapes.drop("geometry", axis=1),
-                geometry=transformed_geometry,
-            )
-        )
+        transformed_gdf = gpd.GeoDataFrame(
+            data=shapes.drop("geometry", axis=1),
+            geometry=transformed_geometry,
+        )
+        # geometry is already in the target coordinate system; drop any transform
+        # carried over in attrs (geopandas >= 1.2 propagates DataFrame.attrs)
+        transformed_gdf.attrs.pop("transform", None)
+        transformed_element = ShapesModel.parse(transformed_gdf, transformations={"global": Identity()})

Versions

spatialdata-plot  0.4.2
geopandas         1.2.0
spatialdata       0.8.0
pandas            3.0.6
shapely           2.2.0
datashader        0.19.1
numpy             2.5.3
Python            3.13.5
OS                macOS-26.7.1-arm64

Activity

  1. lazappi commented on Oct 9, 2026

    @lazappi
    Author

    I think this is the sample as #782 and should also be fixed by #783.

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 working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions