Skip to content

feat: add GeoSeries.intersection() and bigframes.bigquery.st_intersection() #1529

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Mar 26, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion bigframes/bigquery/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
unix_millis,
unix_seconds,
)
from bigframes.bigquery._operations.geo import st_area, st_difference
from bigframes.bigquery._operations.geo import st_area, st_difference, st_intersection
from bigframes.bigquery._operations.json import (
json_extract,
json_extract_array,
Expand All @@ -49,6 +49,7 @@
# geo ops
"st_area",
"st_difference",
"st_intersection",
# json ops
"json_set",
"json_extract",
Expand Down
92 changes: 92 additions & 0 deletions bigframes/bigquery/_operations/geo.py
Original file line number Diff line number Diff line change
Expand Up @@ -211,3 +211,95 @@ def st_difference(
in other.
"""
return series._apply_binary_op(other, ops.geo_st_difference_op)


def st_intersection(
series: bigframes.series.Series, other: bigframes.series.Series
) -> bigframes.series.Series:
"""
Returns a `GEOGRAPHY` that represents the point set intersection of the two
input `GEOGRAPHYs`. Thus, every point in the intersection appears in both
`geography_1` and `geography_2`.

.. note::
BigQuery's Geography functions, like `st_intersection`, interpret the geometry
data type as a point set on the Earth's surface. A point set is a set
of points, lines, and polygons on the WGS84 reference spheroid, with
geodesic edges. See: https://cloud.google.com/bigquery/docs/geospatial-data

**Examples:**

>>> import bigframes as bpd
>>> import bigframes.bigquery as bbq
>>> import bigframes.geopandas
>>> from shapely.geometry import Polygon, LineString, Point
>>> bpd.options.display.progress_bar = None

We can check two GeoSeries against each other, row by row.

>>> s1 = bigframes.geopandas.GeoSeries(
... [
... Polygon([(0, 0), (2, 2), (0, 2)]),
... Polygon([(0, 0), (2, 2), (0, 2)]),
... LineString([(0, 0), (2, 2)]),
... LineString([(2, 0), (0, 2)]),
... Point(0, 1),
... ],
... )
>>> s2 = bigframes.geopandas.GeoSeries(
... [
... Polygon([(0, 0), (1, 1), (0, 1)]),
... LineString([(1, 0), (1, 3)]),
... LineString([(2, 0), (0, 2)]),
... Point(1, 1),
... Point(0, 1),
... ],
... index=range(1, 6),
... )

>>> s1
0 POLYGON ((0 0, 2 2, 0 2, 0 0))
1 POLYGON ((0 0, 2 2, 0 2, 0 0))
2 LINESTRING (0 0, 2 2)
3 LINESTRING (2 0, 0 2)
4 POINT (0 1)
dtype: geometry

>>> s2
1 POLYGON ((0 0, 1 1, 0 1, 0 0))
2 LINESTRING (1 0, 1 3)
3 LINESTRING (2 0, 0 2)
4 POINT (1 1)
5 POINT (0 1)
dtype: geometry

>>> bbq.st_intersection(s1, s2)
0 None
1 POLYGON ((0 0, 0.99954 1, 0 1, 0 0))
2 POINT (1 1.00046)
3 LINESTRING (2 0, 0 2)
4 GEOMETRYCOLLECTION EMPTY
5 None
dtype: geometry

We can also do intersection of each geometry and a single shapely geometry:

>>> bbq.st_intersection(s1, bigframes.geopandas.GeoSeries([Polygon([(0, 0), (1, 1), (0, 1)])]))
0 POLYGON ((0 0, 0.99954 1, 0 1, 0 0))
1 None
2 None
3 None
4 None
dtype: geometry

Args:
other (GeoSeries or geometric object):
The Geoseries (elementwise) or geometric object to find the
intersection with.

Returns:
bigframes.geopandas.GeoSeries:
The Geoseries (elementwise) of the intersection of points in
each aligned geometry with other.
"""
return series._apply_binary_op(other, ops.geo_st_intersection_op)
7 changes: 7 additions & 0 deletions bigframes/core/compile/scalar_op_compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -1037,6 +1037,13 @@ def geo_st_geogpoint_op_impl(x: ibis_types.Value, y: ibis_types.Value):
)


@scalar_op_compiler.register_binary_op(ops.geo_st_intersection_op, pass_op=False)
def geo_st_intersection_op_impl(x: ibis_types.Value, y: ibis_types.Value):
return typing.cast(ibis_types.GeoSpatialValue, x).intersection(
typing.cast(ibis_types.GeoSpatialValue, y)
)


@scalar_op_compiler.register_unary_op(ops.geo_x_op)
def geo_x_op_impl(x: ibis_types.Value):
return typing.cast(ibis_types.GeoSpatialValue, x).x()
Expand Down
3 changes: 3 additions & 0 deletions bigframes/geopandas/geoseries.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,3 +96,6 @@ def to_wkt(self: GeoSeries) -> bigframes.series.Series:

def difference(self: GeoSeries, other: GeoSeries) -> bigframes.series.Series: # type: ignore
return self._apply_binary_op(other, ops.geo_st_difference_op)

def intersection(self: GeoSeries, other: GeoSeries) -> bigframes.series.Series: # type: ignore
return self._apply_binary_op(other, ops.geo_st_intersection_op)
2 changes: 2 additions & 0 deletions bigframes/operations/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@
geo_st_difference_op,
geo_st_geogfromtext_op,
geo_st_geogpoint_op,
geo_st_intersection_op,
geo_x_op,
geo_y_op,
)
Expand Down Expand Up @@ -371,6 +372,7 @@
"geo_st_astext_op",
"geo_st_geogfromtext_op",
"geo_st_geogpoint_op",
"geo_st_intersection_op",
"geo_x_op",
"geo_y_op",
# Numpy ops mapping
Expand Down
4 changes: 4 additions & 0 deletions bigframes/operations/geo_ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,7 @@
dtypes.is_geo_like, dtypes.FLOAT_DTYPE, description="geo-like"
),
)

geo_st_intersection_op = base_ops.create_binary_op(
name="geo_st_intersection", type_signature=op_typing.BinaryGeo()
)
Loading