Skip to content

Commit 9d591c1

Browse files
authored
feat(geoarrow-pyarrow): Support table-like objects in to_geopandas() (#63)
1 parent 0b7c502 commit 9d591c1

File tree

2 files changed

+89
-5
lines changed

2 files changed

+89
-5
lines changed

geoarrow-pyarrow/src/geoarrow/pyarrow/_compute.py

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -603,25 +603,67 @@ def point_coords(obj, dimensions=None):
603603

604604

605605
def to_geopandas(obj):
606-
"""Convert a geoarrow-like array into a ``geopandas.GeoSeries``.
606+
"""Convert a geoarrow-like array or table into a GeoSeries/DataFrame
607607
608+
These are thin wrappers around ``GeoSeries.from_arrow()`` and
609+
``GeoDataFrame.from_arrow()`` where available, falling back on conversion
610+
through WKB if using an older version of GeoPandas or an Arrow array type
611+
that GeoPandas doesn't support.
612+
613+
>>> import pyarrow as pa
608614
>>> import geoarrow.pyarrow as ga
609615
>>> array = ga.as_geoarrow(["POINT (0 1)"])
610616
>>> ga.to_geopandas(array)
611617
0 POINT (0 1)
612618
dtype: geometry
619+
>>> table = pa.table({"geometry": array})
620+
>>> ga.to_geopandas(table)
621+
geometry
622+
0 POINT (0 1)
613623
"""
614624
import geopandas
615625
import pandas as pd
616626

627+
# Heuristic to detect table-like objects
628+
is_table_like = (
629+
hasattr(obj, "schema")
630+
and not callable(obj.schema)
631+
and isinstance(obj.schema, pa.Schema)
632+
)
633+
617634
# Attempt GeoPandas from_arrow first
618635
try:
619-
return geopandas.GeoSeries.from_arrow(obj)
636+
if is_table_like:
637+
return geopandas.GeoDataFrame.from_arrow(obj)
638+
else:
639+
return geopandas.GeoSeries.from_arrow(obj)
620640
except ValueError:
621641
pass
642+
except TypeError:
643+
pass
622644
except AttributeError:
623645
pass
624646

647+
if is_table_like:
648+
obj = pa.table(obj)
649+
is_geo_column = [
650+
isinstance(col.type, _type.GeometryExtensionType) for col in obj.columns
651+
]
652+
new_cols = [
653+
to_geopandas(col) if is_geo else col
654+
for is_geo, col in zip(is_geo_column, obj.columns)
655+
]
656+
657+
# Set the geometry column if there is exactly one geometry column
658+
geo_column_names = [
659+
name for name, is_geo in zip(obj.column_names, is_geo_column) if is_geo
660+
]
661+
geometry = geo_column_names[0] if len(geo_column_names) == 1 else None
662+
return geopandas.GeoDataFrame(
663+
{name: col for name, col in zip(obj.column_names, new_cols)},
664+
geometry=geometry,
665+
)
666+
625667
# Fall back on wkb conversion
626668
wkb_array_or_chunked = as_wkb(obj)
627669

geoarrow-pyarrow/tests/test_geopandas.py

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import pytest
2+
import pyarrow as pa
23
from geoarrow import types
34
import geoarrow.pyarrow as ga
45

@@ -23,18 +24,59 @@ def test_scalar_to_shapely():
2324
assert array[0].to_shapely().wkt == "POINT (30 10)"
2425

2526

26-
def test_to_geopandas():
27-
array = ga.array(["POINT (30 10)"])
27+
def test_to_geopandas_unsupported_type():
28+
# GeoPandas doesn't support geoarrow.wkt, so this goes through the branch
29+
# that handles any GeoPandas failure
30+
array = ga.as_wkt(["POINT (30 10)"])
31+
geoseries = ga.to_geopandas(array)
32+
assert isinstance(geoseries, geopandas.GeoSeries)
33+
assert len(geoseries) == 1
34+
assert geoseries.to_wkt()[0] == "POINT (30 10)"
35+
36+
37+
def test_to_geopandas_using_geopandas():
38+
array = ga.as_wkb(["POINT (30 10)"])
2839
geoseries = ga.to_geopandas(array)
2940
assert isinstance(geoseries, geopandas.GeoSeries)
3041
assert len(geoseries) == 1
3142
assert geoseries.to_wkt()[0] == "POINT (30 10)"
3243

3344

3445
def test_to_geopandas_with_crs():
35-
array = ga.with_crs(ga.array(["POINT (30 10)"]), types.OGC_CRS84)
46+
array = ga.with_crs(ga.as_wkt(["POINT (30 10)"]), types.OGC_CRS84)
47+
geoseries = ga.to_geopandas(array)
48+
assert isinstance(geoseries, geopandas.GeoSeries)
49+
assert len(geoseries) == 1
50+
assert geoseries.to_wkt()[0] == "POINT (30 10)"
51+
assert geoseries.crs.to_authority() == ("OGC", "CRS84")
52+
53+
54+
def test_to_geopandas_with_crs_using_geopandas():
55+
array = ga.with_crs(ga.as_wkb(["POINT (30 10)"]), types.OGC_CRS84)
3656
geoseries = ga.to_geopandas(array)
3757
assert isinstance(geoseries, geopandas.GeoSeries)
3858
assert len(geoseries) == 1
3959
assert geoseries.to_wkt()[0] == "POINT (30 10)"
4060
assert geoseries.crs.to_authority() == ("OGC", "CRS84")
61+
62+
63+
def test_table_to_geopandas_unsupported_type():
64+
# GeoPandas doesn't support geoarrow.wkt, so this goes through the branch
65+
# that handles any GeoPandas failure
66+
table = pa.table({"geom": ga.as_wkt(["POINT (30 10)"])})
67+
gdf = ga.to_geopandas(table)
68+
assert isinstance(gdf, geopandas.GeoDataFrame)
69+
70+
geoseries = gdf.geometry
71+
assert len(geoseries) == 1
72+
assert geoseries.to_wkt()[0] == "POINT (30 10)"
73+
74+
75+
def test_table_to_geopandas_using_geopandas():
76+
table = pa.table({"geom": ga.as_wkb(["POINT (30 10)"])})
77+
gdf = ga.to_geopandas(table)
78+
assert isinstance(gdf, geopandas.GeoDataFrame)
79+
80+
geoseries = gdf.geometry
81+
assert len(geoseries) == 1
82+
assert geoseries.to_wkt()[0] == "POINT (30 10)"

0 commit comments

Comments
 (0)