Skip to content
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
2 changes: 2 additions & 0 deletions bigframes/bigquery/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
st_intersection,
st_isclosed,
st_length,
st_simplify,
)
from bigframes.bigquery._operations.json import (
json_extract,
Expand Down Expand Up @@ -80,6 +81,7 @@
st_intersection,
st_isclosed,
st_length,
st_simplify,
# json ops
json_extract,
json_extract_array,
Expand Down
20 changes: 20 additions & 0 deletions bigframes/bigquery/_operations/geo.py
Original file line number Diff line number Diff line change
Expand Up @@ -675,3 +675,23 @@ def st_length(
series = series._apply_unary_op(ops.GeoStLengthOp(use_spheroid=use_spheroid))
series.name = None
return series


def st_simplify(
geography: "bigframes.series.Series",
tolerance_meters: float,
) -> "bigframes.series.Series":
"""Returns a simplified version of the input geography.
Args:
geography (bigframes.series.Series):
A Series containing GEOGRAPHY data.
tolerance_meters (float):
A float64 value indicating the tolerance in meters.
Returns:
a Series containing the simplified GEOGRAPHY data.
"""
return geography._apply_unary_op(
ops.GeoStSimplifyOp(tolerance_meters=tolerance_meters)
)
14 changes: 14 additions & 0 deletions bigframes/core/compile/ibis_compiler/operations/geo_ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,12 @@ def geo_st_isclosed_op_impl(x: ibis_types.Value):
return st_isclosed(x)


@register_unary_op(ops.GeoStSimplifyOp, pass_op=True)
def st_simplify_op_impl(x: ibis_types.Value, op: ops.GeoStSimplifyOp):
x = cast(ibis_types.GeoSpatialValue, x)
return st_simplify(x, op.tolerance_meters)


@register_unary_op(ops.geo_x_op)
def geo_x_op_impl(x: ibis_types.Value):
return cast(ibis_types.GeoSpatialValue, x).x()
Expand Down Expand Up @@ -157,3 +163,11 @@ def st_length(geog: ibis_dtypes.geography, use_spheroid: bool) -> ibis_dtypes.fl
@ibis_udf.scalar.builtin
def st_isclosed(a: ibis_dtypes.geography) -> ibis_dtypes.boolean: # type: ignore
"""Checks if a geography is closed."""


@ibis_udf.scalar.builtin
def st_simplify(
geography: ibis_dtypes.geography, # type: ignore
tolerance_meters: ibis_dtypes.float, # type: ignore
) -> ibis_dtypes.geography: # type: ignore
...
5 changes: 5 additions & 0 deletions bigframes/geopandas/geoseries.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,3 +123,8 @@ def distance(self: GeoSeries, other: GeoSeries) -> bigframes.series.Series: # t

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

def simplify(self, tolerance, preserve_topology=True):
raise NotImplementedError(
f"GeoSeries.simplify is not supported. Use bigframes.bigquery.st_simplify(series, tolerance_meters), instead. {constants.FEEDBACK_LINK}"
)
2 changes: 2 additions & 0 deletions bigframes/operations/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@
GeoStBufferOp,
GeoStDistanceOp,
GeoStLengthOp,
GeoStSimplifyOp,
)
from bigframes.operations.json_ops import (
JSONExtract,
Expand Down Expand Up @@ -416,6 +417,7 @@
"geo_st_isclosed_op",
"GeoStBufferOp",
"GeoStLengthOp",
"GeoStSimplifyOp",
"geo_x_op",
"geo_y_op",
"GeoStDistanceOp",
Expand Down
9 changes: 9 additions & 0 deletions bigframes/operations/geo_ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,3 +133,12 @@ class GeoStLengthOp(base_ops.UnaryOp):

def output_type(self, *input_types: dtypes.ExpressionType) -> dtypes.ExpressionType:
return dtypes.FLOAT_DTYPE


@dataclasses.dataclass(frozen=True)
class GeoStSimplifyOp(base_ops.UnaryOp):
name = "st_simplify"
tolerance_meters: float

def output_type(self, *input_types: dtypes.ExpressionType) -> dtypes.ExpressionType:
return dtypes.GEO_DTYPE
15 changes: 11 additions & 4 deletions specs/2025-08-04-geoseries-scalars.md
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,10 @@ Raster functions: Functions for analyzing geospatial rasters using geographies.
### Implementing a new scalar geography operation

- [ ] **Define the operation dataclass:**
- [ ] In `bigframes/operations/geo_ops.py`, create a new dataclass inheriting from `base_ops.UnaryOp` or `base_ops.BinaryOp`.
- [ ] In `bigframes/operations/geo_ops.py`, create a new dataclass
inheriting from `base_ops.UnaryOp` or `base_ops.BinaryOp`. Note that
BinaryOp is for methods that take two **columns**. Any literal values can
be passed as parameters to a UnaryOp.
- [ ] Define the `name` of the operation and any parameters it requires.
- [ ] Implement the `output_type` method to specify the data type of the result.
- [ ] **Export the new operation:**
Expand All @@ -283,13 +286,17 @@ Raster functions: Functions for analyzing geospatial rasters using geographies.
- [ ] Add a comprehensive docstring with examples.
- [ ] In `bigframes/bigquery/__init__.py`, import your new user-facing function and add it to the `__all__` list.
- [ ] For a `GeoSeries` property or method:
- [ ] In `bigframes/geopandas/geoseries.py`, create the property or method.
- [ ] In `bigframes/geopandas/geoseries.py`, create the property or
method. Omit the docstring.
- [ ] If the operation is not possible to be supported, such as if the
geopandas method returns values in units corresponding to the
coordinate system rather than meters that BigQuery uses, raise a
`NotImplementedError` with a helpful message.
`NotImplementedError` with a helpful message. Likewise, if a
required parameter takes a value in terms of the coordinate
system, but BigQuery uses meters, raise a `NotImplementedError`.
- [ ] Otherwise, call `series._apply_unary_op` or `series._apply_binary_op`, passing the operation dataclass.
- [ ] Add a comprehensive docstring with examples.
- [ ] Add a comprehensive docstring with examples to the superclass in
`third_party/bigframes_vendored/geopandas/geoseries.py`.
- [ ] **Add Tests:**
- [ ] Add system tests in `tests/system/small/bigquery/test_geo.py` or `tests/system/small/geopandas/test_geoseries.py` to verify the end-to-end functionality. Test various inputs, including edge cases and `NULL` values.
- [ ] If you are overriding a pandas or GeoPandas property and raising `NotImplementedError`, add a unit test to ensure the correct error is raised.
Expand Down
9 changes: 9 additions & 0 deletions tests/system/small/bigquery/test_geo.py
Original file line number Diff line number Diff line change
Expand Up @@ -480,3 +480,12 @@ def test_st_buffer(session):
result = bbq.st_buffer(geoseries, 1000).to_pandas()
assert result.iloc[0].geom_type == "Polygon"
assert result.iloc[1].geom_type == "Polygon"


def test_st_simplify(session):
geoseries = bigframes.geopandas.GeoSeries(
[LineString([(0, 0), (1, 1), (2, 0)])], session=session
)
result = bbq.st_simplify(geoseries, 100000).to_pandas()
assert len(result.index) == 1
assert result.isna().sum() == 0
28 changes: 28 additions & 0 deletions third_party/bigframes_vendored/geopandas/geoseries.py
Original file line number Diff line number Diff line change
Expand Up @@ -496,3 +496,31 @@ def is_closed(self: GeoSeries) -> bigframes.series.Series:
``bigframes.bigquery.st_isclosed(series)``, instead.
"""
raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE)

def simplify(self, tolerance: float, preserve_topology: bool = True) -> bigframes.series.Series: # type: ignore
"""[Not Implemented] Use ``bigframes.bigquery.st_simplify(series, tolerance_meters)``,
instead to set the tolerance in meters.
In GeoPandas, this returns a GeoSeries containing a simplified
representation of each geometry.
Args:
tolerance (float):
All parts of a simplified geometry will be no more than
tolerance distance from the original. It has the same units as
the coordinate reference system of the GeoSeries. For example,
using tolerance=100 in a projected CRS with meters as units
means a distance of 100 meters in reality.
preserve_topology (bool):
Default True. False uses a quicker algorithm, but may produce
self-intersecting or otherwise invalid geometries.
Returns:
bigframes.geopandas.GeoSeries:
Series of simplified geometries.
Raises:
NotImplementedError:
GeoSeries.simplify is not supported. Use bigframes.bigquery.st_simplify(series, tolerance_meters), instead.
"""
raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE)