Hacktoberfest 2026:メンテナが10月に向けて印を付けた、オープンで初心者向けの issue。 Hacktoberfest の issue を見る

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

クローズ 初心者向け
#781 コメント 0 件 リアクション 0 件 担当者 0 名 GitHub で見る

まだ誰も着手していません。

評価

難易度
2/5
見積もり時間
1〜3時間
初心者へのやさしさ
72/100
issue の種類
バグ
明瞭さ
明確に書かれている
活発さ
活発
技術スタック
python

調査の方向性

spatialdata_plot/pl/render.py の _render_shapes にある datashader ブランチから始めてください。そこで ShapesModel.parse を使って transformed_element が作成されています。issue の12,000シェイプの再現コードを、グローバルではない座標系で geopandas 1.2.0 と 1.1.4 の両方で実行してください。両方のバージョンで描画でき、コピーされた transform 属性が削除され、"global" の恒等変換が明示的に設定されていれば完了です。

索引モデルが issue の本文から書いたものです。

説明

bug

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
主要言語
Python
スター
86
フォーク
22
平均マージ
4日 5時間
マージ済み PR(30日)
5

環境構築

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

scverse/spatialdata-plot のほかの issue

scverse/spatialdata-plot の issue をすべて見る

似ている issue

Python の issue をもっと見る

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。