Skip to content

Commit e04bf3b

Browse files
authored
fix(python): Fix __geo_interface__ export for GeoScalar (#1384)
1 parent 637f347 commit e04bf3b

File tree

6 files changed

+64
-25
lines changed

6 files changed

+64
-25
lines changed

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

python/Cargo.lock

Lines changed: 15 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

python/tests/core/test_scalar.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
1+
import geodatasets
12
import geopandas as gpd
23
import pyarrow as pa
34
import shapely
45
from arro3.core import Scalar
56
from geoarrow.rust.core import GeoArray, point
67
from geoarrow.types.type_pyarrow import registered_extension_types
78

9+
from tests.utils import geo_interface_equals
10+
811

912
def test_eq():
1013
geoms = shapely.points([1, 2, 3], [4, 5, 6])
@@ -31,3 +34,19 @@ def test_repr():
3134
geoms = shapely.points([1, 2, 3], [4, 5, 6])
3235
arr = GeoArray.from_arrow(gpd.GeoSeries(geoms).to_arrow("geoarrow"))
3336
assert repr(arr[0]) == 'GeoScalar(Point(dimension="XY", coord_type="interleaved"))'
37+
38+
39+
def test_geo_interface():
40+
geoms = shapely.points([1, 2, 3], [4, 5, 6])
41+
gs = gpd.GeoSeries(geoms)
42+
arr = GeoArray.from_arrow(gs.to_arrow("geoarrow"))
43+
assert geo_interface_equals(gs[0].__geo_interface__, arr[0].__geo_interface__)
44+
45+
46+
def test_geo_interface_polygon():
47+
gdf = gpd.read_file(geodatasets.get_path("ny.bb"))
48+
arr = GeoArray.from_arrow(gdf.geometry.to_arrow("geoarrow"))
49+
assert geo_interface_equals(
50+
gdf.geometry.iloc[0].__geo_interface__,
51+
arr[0].__geo_interface__,
52+
)

python/tests/utils.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import json
12
from pathlib import Path
23

34

@@ -15,3 +16,10 @@ def get_repo_root() -> Path:
1516
REPO_ROOT = get_repo_root()
1617
FIXTURES_DIR = REPO_ROOT / "fixtures"
1718

19+
20+
def geo_interface_equals(d1, d2):
21+
"""Compare two __geo_interface__ dictionaries for equality.
22+
23+
This handles list/tuple equality
24+
"""
25+
return json.loads(json.dumps(d1)) == json.loads(json.dumps(d2))

rust/pyo3-geoarrow/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ categories = []
1212
rust-version = { workspace = true }
1313

1414
[features]
15+
default = ["geozero", "geojson"]
1516
geozero = ["dep:geozero", "geoarrow-array/geozero"]
1617

1718
[dependencies]
@@ -24,6 +25,8 @@ geo-types = { workspace = true }
2425
geoarrow-array = { workspace = true }
2526
geoarrow-cast = { workspace = true }
2627
geoarrow-schema = { workspace = true }
28+
geoarrow-expr-geo = { workspace = true }
29+
geojson = { workspace = true, optional = true }
2730
geozero = { workspace = true, optional = true }
2831
pyo3 = { workspace = true, features = ["chrono", "indexmap"] }
2932
pyo3-arrow = { workspace = true }

rust/pyo3-geoarrow/src/scalar/mod.rs

Lines changed: 17 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@ mod bounding_rect;
66
use std::io::Write;
77
use std::sync::Arc;
88

9-
use geoarrow_array::GeoArrowArray;
109
use geoarrow_array::cast::AsGeoArrowArray;
10+
use geoarrow_array::{GeoArrowArray, GeoArrowArrayAccessor, downcast_geoarrow_array};
11+
use geoarrow_expr_geo::util::to_geo::geometry_to_geo;
1112
use geoarrow_schema::GeoArrowType;
1213
use geoarrow_schema::error::GeoArrowError;
1314
use pyo3::exceptions::{PyIOError, PyValueError};
@@ -82,12 +83,13 @@ impl PyGeoScalar {
8283
}
8384
}
8485

85-
#[cfg(feature = "geozero")]
86+
#[cfg(feature = "geojson")]
8687
#[getter]
8788
fn __geo_interface__<'py>(&'py self, py: Python<'py>) -> PyGeoArrowResult<Bound<'py, PyAny>> {
88-
let json_string = to_json(&self.0).map_err(|err| GeoArrowError::External(Box::new(err)))?;
89+
let geojson_geometry = scalar_to_geojson(&self.0)?;
90+
let geojson_string = serde_json::to_string(&geojson_geometry)?;
8991
let json_mod = py.import(intern!(py, "json"))?;
90-
Ok(json_mod.call_method1(intern!(py, "loads"), (json_string,))?)
92+
Ok(json_mod.call_method1(intern!(py, "loads"), (geojson_string,))?)
9193
}
9294

9395
#[cfg(feature = "geozero")]
@@ -182,25 +184,15 @@ fn process_svg_geom<W: Write>(
182184
}
183185
}
184186

185-
#[cfg(feature = "geozero")]
186-
fn to_json(arr: &dyn GeoArrowArray) -> geozero::error::Result<String> {
187-
use GeoArrowType::*;
188-
use geozero::ToJson;
189-
match arr.data_type() {
190-
Point(_) => arr.as_point().to_json(),
191-
LineString(_) => arr.as_line_string().to_json(),
192-
Polygon(_) => arr.as_polygon().to_json(),
193-
MultiPoint(_) => arr.as_multi_point().to_json(),
194-
MultiLineString(_) => arr.as_multi_line_string().to_json(),
195-
MultiPolygon(_) => arr.as_multi_polygon().to_json(),
196-
GeometryCollection(_) => arr.as_geometry_collection().to_json(),
197-
Geometry(_) => arr.as_geometry().to_json(),
198-
Rect(_) => arr.as_rect().to_json(),
199-
Wkb(_) => arr.as_wkb::<i32>().to_json(),
200-
LargeWkb(_) => arr.as_wkb::<i64>().to_json(),
201-
WkbView(_) => arr.as_wkb_view().to_json(),
202-
Wkt(_) => arr.as_wkt::<i32>().to_json(),
203-
LargeWkt(_) => arr.as_wkt::<i64>().to_json(),
204-
WktView(_) => arr.as_wkt_view().to_json(),
205-
}
187+
#[cfg(feature = "geojson")]
188+
fn scalar_to_geojson(scalar: &dyn GeoArrowArray) -> PyGeoArrowResult<geojson::Geometry> {
189+
downcast_geoarrow_array!(scalar, impl_to_geojson)
190+
}
191+
192+
#[cfg(feature = "geojson")]
193+
fn impl_to_geojson<'a>(
194+
array: &'a impl GeoArrowArrayAccessor<'a>,
195+
) -> PyGeoArrowResult<geojson::Geometry> {
196+
let geo_geom = geometry_to_geo(&array.value(0)?)?;
197+
Ok((&geo_geom).into())
206198
}

0 commit comments

Comments
 (0)