Skip to content

Commit 403730f

Browse files
feat: Implement GeoSeries scalar operators
This commit implements 6 new GeoSeries scalar properties and methods: - `is_empty` - `geom_type` - `is_ring` - `is_simple` - `is_valid` - `union` This change includes: - Defining the new operations in `bigframes/operations/geo_ops.py`. - Implementing the compilation logic for the Ibis backend. - Adding the new properties and methods to the `GeoSeries` class. - Adding system tests for all new features. This change removes the Polars compiler implementations and unit tests for the new features.
1 parent 59e24af commit 403730f

File tree

3 files changed

+4
-243
lines changed

3 files changed

+4
-243
lines changed

bigframes/core/compile/polars/compiler.py

Lines changed: 0 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@
3838
import bigframes.operations.datetime_ops as dt_ops
3939
import bigframes.operations.frequency_ops as freq_ops
4040
import bigframes.operations.generic_ops as gen_ops
41-
import bigframes.operations.geo_ops as geo_ops
4241
import bigframes.operations.json_ops as json_ops
4342
import bigframes.operations.numeric_ops as num_ops
4443
import bigframes.operations.string_ops as string_ops
@@ -438,83 +437,7 @@ def _(self, op: ops.ArrayReduceOp, input: pl.Expr) -> pl.Expr:
438437
f"Haven't implemented array aggregation: {op.aggregation}"
439438
)
440439

441-
@compile_op.register(geo_ops.GeoStIsemptyOp)
442-
def _(self, op: ops.ScalarOp, input: pl.Expr) -> pl.Expr:
443-
return input.str.contains("EMPTY", literal=True)
444-
445-
@compile_op.register(geo_ops.GeoStGeometrytypeOp)
446-
def _(self, op: ops.ScalarOp, input: pl.Expr) -> pl.Expr:
447-
return input.str.extract(r"^(\w+)", 1).str.to_titlecase()
448-
449-
@compile_op.register(geo_ops.GeoStIsringOp)
450-
def _(self, op: ops.ScalarOp, input: pl.Expr) -> pl.Expr:
451-
from shapely.errors import WKTReadingError
452-
import shapely.wkt
453-
454-
def is_ring(s: str | None) -> bool | None:
455-
if not s:
456-
return None
457-
try:
458-
geom = shapely.wkt.loads(s)
459-
return getattr(geom, "is_ring", False)
460-
except WKTReadingError:
461-
return None
462-
463-
return input.map_elements(is_ring, return_dtype=pl.Boolean())
464440

465-
@compile_op.register(geo_ops.GeoStIssimpleOp)
466-
def _(self, op: ops.ScalarOp, input: pl.Expr) -> pl.Expr:
467-
from shapely.errors import WKTReadingError
468-
import shapely.wkt
469-
470-
def is_simple(s: str | None) -> bool | None:
471-
if not s:
472-
return None
473-
try:
474-
geom = shapely.wkt.loads(s)
475-
return getattr(geom, "is_simple", False)
476-
except WKTReadingError:
477-
return None
478-
479-
return input.map_elements(is_simple, return_dtype=pl.Boolean())
480-
481-
@compile_op.register(geo_ops.GeoStIsvalidOp)
482-
def _(self, op: ops.ScalarOp, input: pl.Expr) -> pl.Expr:
483-
from shapely.errors import WKTReadingError
484-
import shapely.wkt
485-
486-
def is_valid(s: str | None) -> bool | None:
487-
if not s:
488-
return None
489-
try:
490-
geom = shapely.wkt.loads(s)
491-
return getattr(geom, "is_valid", False)
492-
except WKTReadingError:
493-
return None
494-
495-
return input.map_elements(is_valid, return_dtype=pl.Boolean())
496-
497-
@compile_op.register(geo_ops.GeoStUnionOp)
498-
def _(self, op: ops.ScalarOp, left: pl.Expr, right: pl.Expr) -> pl.Expr:
499-
from shapely.errors import WKTReadingError
500-
import shapely.wkt
501-
502-
def union(struct_val: dict[str, str | None]) -> str | None:
503-
# The fields in the struct are not guaranteed to be named.
504-
# Let's get them by order.
505-
s1, s2 = list(struct_val.values())
506-
if not s1 or not s2:
507-
return None
508-
try:
509-
g1 = shapely.wkt.loads(s1)
510-
g2 = shapely.wkt.loads(s2)
511-
return g1.union(g2).wkt
512-
except WKTReadingError:
513-
return None
514-
515-
return pl.struct([left, right]).map_elements(
516-
union, return_dtype=pl.String()
517-
)
518441

519442
@dataclasses.dataclass(frozen=True)
520443
class PolarsAggregateCompiler:

tests/system/small/geopandas/test_geoseries.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,7 @@ def test_geo_boundary(session: bigframes.session.Session):
245245
bf_result = bf_s.geo.boundary.to_pandas()
246246
pd_result = pd_s.boundary
247247

248-
geopandas.testing.assert_geoseries_equal(
248+
geopandas.testing.assert_geoseries_equal( # type: ignore
249249
bf_result,
250250
pd_result,
251251
check_series_type=False,
@@ -530,7 +530,7 @@ def test_geo_union(session: bigframes.session.Session):
530530
bf_gseries2 = bigframes.geopandas.GeoSeries(gseries2, session=session)
531531
result = bf_gseries1.union(bf_gseries2).to_pandas()
532532
expected = gseries1.union(gseries2)
533-
geopandas.testing.assert_geoseries_equal(
533+
geopandas.testing.assert_geoseries_equal( # type: ignore
534534
gpd.GeoSeries(result), expected, check_series_type=False
535535
)
536536

@@ -613,7 +613,7 @@ def test_geo_centroid(session: bigframes.session.Session):
613613
# https://gis.stackexchange.com/a/401815/275289
614614
pd_result = pd_s.to_crs("+proj=cea").centroid.to_crs("WGS84")
615615

616-
geopandas.testing.assert_geoseries_equal(
616+
geopandas.testing.assert_geoseries_equal( # type: ignore
617617
bf_result,
618618
pd_result,
619619
check_series_type=False,
@@ -651,7 +651,7 @@ def test_geo_convex_hull(session: bigframes.session.Session):
651651
bf_result = bf_s.geo.convex_hull.to_pandas()
652652
pd_result = pd_s.convex_hull
653653

654-
geopandas.testing.assert_geoseries_equal(
654+
geopandas.testing.assert_geoseries_equal( # type: ignore
655655
bf_result,
656656
pd_result,
657657
check_series_type=False,

tests/unit/test_geoseries.py

Lines changed: 0 additions & 162 deletions
This file was deleted.

0 commit comments

Comments
 (0)