From 77d9fea6f927f5addba2d24c945a7e94abc3736c Mon Sep 17 00:00:00 2001 From: Tim Swena Date: Mon, 21 Apr 2025 10:28:41 -0500 Subject: [PATCH 01/11] feat: add `bigframes.bigquery.st_distance` function --- bigframes/bigquery/__init__.py | 8 +- bigframes/bigquery/_operations/geo.py | 96 +++++++++++++++++++ bigframes/core/compile/scalar_op_compiler.py | 12 +++ bigframes/geopandas/geoseries.py | 22 +---- bigframes/operations/__init__.py | 2 + bigframes/operations/geo_ops.py | 11 +++ .../bigframes_vendored/geopandas/geoseries.py | 50 ++++++++++ 7 files changed, 183 insertions(+), 18 deletions(-) diff --git a/bigframes/bigquery/__init__.py b/bigframes/bigquery/__init__.py index c04350275d..6eb725975e 100644 --- a/bigframes/bigquery/__init__.py +++ b/bigframes/bigquery/__init__.py @@ -27,7 +27,12 @@ unix_millis, unix_seconds, ) -from bigframes.bigquery._operations.geo import st_area, st_difference, st_intersection +from bigframes.bigquery._operations.geo import ( + st_area, + st_difference, + st_distance, + st_intersection, +) from bigframes.bigquery._operations.json import ( json_extract, json_extract_array, @@ -49,6 +54,7 @@ # geo ops "st_area", "st_difference", + "st_distance", "st_intersection", # json ops "json_set", diff --git a/bigframes/bigquery/_operations/geo.py b/bigframes/bigquery/_operations/geo.py index f2d8b7b577..4932207544 100644 --- a/bigframes/bigquery/_operations/geo.py +++ b/bigframes/bigquery/_operations/geo.py @@ -213,6 +213,102 @@ def st_difference( return series._apply_binary_op(other, ops.geo_st_difference_op) +def st_distance( + series: bigframes.series.Series, + other: bigframes.series.Series, + *, + use_spheroid: bool = False, +) -> bigframes.series.Series: + """ + Returns the shortest distance in meters between two non-empty + ``GEOGRAPHY``s. + + **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( + ... [ + ... Point(0, 0), + ... Point(0.00001, 0), + ... Point(0.00002, 0), + ... ], + ... ) + >>> 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_distance(s1, s2, use_spheroid=True) + 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: + series: + A ``GEOGRAPHY`` type Series or GeoSeries. + other: + A ``GEOGRAPHY`` type Series or GeoSeries. + use_spheroid (optional, default ``False``): + Determines how this function measures distance. If ``use_spheroid`` + is False, the function measures distance on the surface of a perfect + sphere. If ``use_spheroid`` is True, the function measures distance + on the surface of the `WGS84 spheroid + `_. The + default value of ``use_spheroid`` is False. + + Returns: + bigframes.pandas.Series: + The Series (elementwise) of the smallest distance between + each aligned geometry with other. + """ + return series._apply_binary_op( + other, ops.GeoStDistanceOp(use_spheroid=use_spheroid) + ) + + def st_intersection( series: bigframes.series.Series, other: bigframes.series.Series ) -> bigframes.series.Series: diff --git a/bigframes/core/compile/scalar_op_compiler.py b/bigframes/core/compile/scalar_op_compiler.py index eda70f5cf1..8243627a91 100644 --- a/bigframes/core/compile/scalar_op_compiler.py +++ b/bigframes/core/compile/scalar_op_compiler.py @@ -1023,6 +1023,13 @@ def geo_st_difference_op_impl(x: ibis_types.Value, y: ibis_types.Value): ) +@scalar_op_compiler.register_binary_op(ops.GeoStDistanceOp, pass_op=True) +def geo_st_distance_op_impl( + x: ibis_types.Value, y: ibis_types.Value, op: ops.GeoStDistanceOp +): + return st_distance(x, y, op.use_spheroid) + + @scalar_op_compiler.register_unary_op(ops.geo_st_geogfromtext_op) def geo_st_geogfromtext_op_impl(x: ibis_types.Value): # Ibis doesn't seem to provide a dedicated method to cast from string to geography, @@ -1989,6 +1996,11 @@ def st_boundary(a: ibis_dtypes.geography) -> ibis_dtypes.geography: # type: ign """Find the boundary of a geography.""" +@ibis_udf.scalar.builtin +def st_distance(a: ibis_dtypes.geography, b: ibis_dtypes.geography, use_spheroid: bool) -> ibis_dtypes.float: # type: ignore + """Convert string to geography.""" + + @ibis_udf.scalar.builtin def unix_micros(a: ibis_dtypes.timestamp) -> int: # type: ignore """Convert a timestamp to microseconds""" diff --git a/bigframes/geopandas/geoseries.py b/bigframes/geopandas/geoseries.py index c93a02deb8..38ebda7d92 100644 --- a/bigframes/geopandas/geoseries.py +++ b/bigframes/geopandas/geoseries.py @@ -47,23 +47,6 @@ def y(self) -> bigframes.series.Series: # we can. @property def area(self, crs=None) -> bigframes.series.Series: # type: ignore - """Returns a Series containing the area of each geometry in the GeoSeries - expressed in the units of the CRS. - - Args: - crs (optional): - Coordinate Reference System of the geometry objects. Can be - anything accepted by pyproj.CRS.from_user_input(), such as an - authority string (eg “EPSG:4326”) or a WKT string. - - Returns: - bigframes.pandas.Series: - Series of float representing the areas. - - Raises: - NotImplementedError: - GeoSeries.area is not supported. Use bigframes.bigquery.st_area(series), instead. - """ raise NotImplementedError( f"GeoSeries.area is not supported. Use bigframes.bigquery.st_area(series), instead. {constants.FEEDBACK_LINK}" ) @@ -97,5 +80,10 @@ 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 distance(self: GeoSeries, other: GeoSeries) -> bigframes.series.Series: # type: ignore + raise NotImplementedError( + f"GeoSeries.distance is not supported. Use bigframes.bigquery.st_distance(series, other), instead. {constants.FEEDBACK_LINK}" + ) + def intersection(self: GeoSeries, other: GeoSeries) -> bigframes.series.Series: # type: ignore return self._apply_binary_op(other, ops.geo_st_intersection_op) diff --git a/bigframes/operations/__init__.py b/bigframes/operations/__init__.py index 3e0ebd5089..74ff5c0f98 100644 --- a/bigframes/operations/__init__.py +++ b/bigframes/operations/__init__.py @@ -96,6 +96,7 @@ geo_st_intersection_op, geo_x_op, geo_y_op, + GeoStDistanceOp, ) from bigframes.operations.json_ops import ( JSONExtract, @@ -375,6 +376,7 @@ "geo_st_intersection_op", "geo_x_op", "geo_y_op", + "GeoStDistanceOp", # Numpy ops mapping "NUMPY_TO_BINOP", "NUMPY_TO_OP", diff --git a/bigframes/operations/geo_ops.py b/bigframes/operations/geo_ops.py index 6f988c2585..98da9099cd 100644 --- a/bigframes/operations/geo_ops.py +++ b/bigframes/operations/geo_ops.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +import dataclasses + from bigframes import dtypes from bigframes.operations import base_ops import bigframes.operations.type as op_typing @@ -69,3 +71,12 @@ geo_st_intersection_op = base_ops.create_binary_op( name="geo_st_intersection", type_signature=op_typing.BinaryGeo() ) + + +@dataclasses.dataclass(frozen=True) +class GeoStDistanceOp(base_ops.BinaryOp): + name = "st_distance" + use_spheroid: bool + + def output_type(self, *input_types: dtypes.ExpressionType) -> dtypes.ExpressionType: + return dtypes.FLOAT_DTYPE diff --git a/third_party/bigframes_vendored/geopandas/geoseries.py b/third_party/bigframes_vendored/geopandas/geoseries.py index 4ad4f383cf..a5667188be 100644 --- a/third_party/bigframes_vendored/geopandas/geoseries.py +++ b/third_party/bigframes_vendored/geopandas/geoseries.py @@ -37,6 +37,33 @@ class GeoSeries: e.g. ``name``. """ + # GeoSeries.area overrides Series.area with something totally different. + # Ignore this type error, as we are trying to be as close to geopandas as + # we can. + @property + def area(self, crs=None) -> bigframes.series.Series: # type: ignore + """[Not Implemented]: Use ``bigframes.bigquery.st_area(series)``, + instead to return the area in square meters. + + In GeoPandas, this returns a Series containing the area of each geometry + in the GeoSeries expressed in the units of the CRS. + + Args: + crs (optional): + Coordinate Reference System of the geometry objects. Can be + anything accepted by pyproj.CRS.from_user_input(), such as an + authority string (eg “EPSG:4326”) or a WKT string. + + Returns: + bigframes.pandas.Series: + Series of float representing the areas. + + Raises: + NotImplementedError: + GeoSeries.area is not supported. Use bigframes.bigquery.st_area(series), instead. + """ + raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) + @property def x(self) -> bigframes.series.Series: """Return the x location of point geometries in a GeoSeries @@ -348,6 +375,29 @@ def difference(self: GeoSeries, other: GeoSeries) -> GeoSeries: # type: ignore """ raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) + def distance(self: GeoSeries, other: GeoSeries) -> bigframes.series.Series: + """[Not Implemented]: Use ``bigframes.bigquery.st_distance(series, other)``, + instead to return the shorted distance between two ``GEOGRAPHY``s in + meters. + + In GeoPandas, this returns a Series of the distancies between each + aligned geometry in the expressed in the units of the CRS. + + Args: + other: + The Geoseries (elementwise) or geometric object to find the distance to. + + Returns: + bigframes.pandas.Series: + Series of float representing the distances. + + Raises: + NotImplementedError: + GeoSeries.distance is not supported. Use + ``bigframes.bigquery.st_distance(series, other)``, instead. + """ + raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) + def intersection(self: GeoSeries, other: GeoSeries) -> GeoSeries: # type: ignore """ Returns a GeoSeries of the intersection of points in each aligned From 3d68b7c9eaf97d07a8c577346e874cb0f2f4919a Mon Sep 17 00:00:00 2001 From: Tim Swena Date: Mon, 21 Apr 2025 15:01:43 -0500 Subject: [PATCH 02/11] fix docstring --- bigframes/bigquery/_operations/geo.py | 2 +- third_party/bigframes_vendored/geopandas/geoseries.py | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/bigframes/bigquery/_operations/geo.py b/bigframes/bigquery/_operations/geo.py index 4932207544..5508cd72ab 100644 --- a/bigframes/bigquery/_operations/geo.py +++ b/bigframes/bigquery/_operations/geo.py @@ -221,7 +221,7 @@ def st_distance( ) -> bigframes.series.Series: """ Returns the shortest distance in meters between two non-empty - ``GEOGRAPHY``s. + ``GEOGRAPHY`` objects. **Examples:** diff --git a/third_party/bigframes_vendored/geopandas/geoseries.py b/third_party/bigframes_vendored/geopandas/geoseries.py index a5667188be..12374a14c6 100644 --- a/third_party/bigframes_vendored/geopandas/geoseries.py +++ b/third_party/bigframes_vendored/geopandas/geoseries.py @@ -42,7 +42,7 @@ class GeoSeries: # we can. @property def area(self, crs=None) -> bigframes.series.Series: # type: ignore - """[Not Implemented]: Use ``bigframes.bigquery.st_area(series)``, + """[Not Implemented] Use ``bigframes.bigquery.st_area(series)``, instead to return the area in square meters. In GeoPandas, this returns a Series containing the area of each geometry @@ -376,9 +376,10 @@ def difference(self: GeoSeries, other: GeoSeries) -> GeoSeries: # type: ignore raise NotImplementedError(constants.ABSTRACT_METHOD_ERROR_MESSAGE) def distance(self: GeoSeries, other: GeoSeries) -> bigframes.series.Series: - """[Not Implemented]: Use ``bigframes.bigquery.st_distance(series, other)``, - instead to return the shorted distance between two ``GEOGRAPHY``s in - meters. + """ + [Not Implemented] Use ``bigframes.bigquery.st_distance(series, other)`` + instead to return the shorted distance between two + ``GEOGRAPHY`` objects in meters. In GeoPandas, this returns a Series of the distancies between each aligned geometry in the expressed in the units of the CRS. From 7a2998f6dcaf178ae589edc3b223585bb56f2d52 Mon Sep 17 00:00:00 2001 From: Tim Swena Date: Mon, 21 Apr 2025 16:54:38 -0500 Subject: [PATCH 03/11] add tests --- bigframes/bigquery/_operations/geo.py | 110 +++------ bigframes/dtypes.py | 20 +- tests/system/small/bigquery/test_geo.py | 219 +++++++++++++----- .../system/small/geopandas/test_geoseries.py | 29 +++ tests/unit/core/test_dtypes.py | 17 ++ 5 files changed, 247 insertions(+), 148 deletions(-) diff --git a/bigframes/bigquery/_operations/geo.py b/bigframes/bigquery/_operations/geo.py index 5508cd72ab..f79c23647b 100644 --- a/bigframes/bigquery/_operations/geo.py +++ b/bigframes/bigquery/_operations/geo.py @@ -14,6 +14,10 @@ from __future__ import annotations +from typing import Union + +import shapely + from bigframes import operations as ops import bigframes.dtypes import bigframes.geopandas @@ -95,7 +99,8 @@ def st_area(series: bigframes.series.Series) -> bigframes.series.Series: def st_difference( - series: bigframes.series.Series, other: bigframes.series.Series + series: bigframes.series.Series, + other: Union[bigframes.series.Series, shapely.Geometry], ) -> bigframes.series.Series: """ Returns a `GEOGRAPHY` that represents the point set difference of @@ -166,39 +171,15 @@ def st_difference( 5 None dtype: geometry - We can also check difference of single shapely geometries: - - >>> polygon_s1 = bigframes.geopandas.GeoSeries( - ... [ - ... Polygon([(0, 0), (10, 0), (10, 10), (0, 0)]) - ... ] - ... ) - >>> polygon_s2 = bigframes.geopandas.GeoSeries( - ... [ - ... Polygon([(4, 2), (6, 2), (8, 6), (4, 2)]) - ... ] - ... ) - - >>> polygon_s1 - 0 POLYGON ((0 0, 10 0, 10 10, 0 0)) - dtype: geometry - - >>> polygon_s2 - 0 POLYGON ((4 2, 6 2, 8 6, 4 2)) - dtype: geometry - - >>> bbq.st_difference(polygon_s1, polygon_s2) - 0 POLYGON ((0 0, 10 0, 10 10, 0 0), (8 6, 6 2, 4... - dtype: geometry - Additionally, we can check difference of a GeoSeries against a single shapely geometry: - >>> bbq.st_difference(s1, polygon_s2) - 0 POLYGON ((0 0, 2 2, 0 2, 0 0)) - 1 None - 2 None - 3 None - 4 None + >>> polygon = Polygon([(0, 0), (10, 0), (10, 10), (0, 0)]) + >>> bbq.st_difference(s1, polygon) + 0 POLYGON ((1.97082 2.00002, 0 2, 0 0, 1.97082 2... + 1 POLYGON ((1.97082 2.00002, 0 2, 0 0, 1.97082 2... + 2 GEOMETRYCOLLECTION EMPTY + 3 LINESTRING (0.99265 1.00781, 0 2) + 4 POINT (0 1) dtype: geometry Args: @@ -215,7 +196,7 @@ def st_difference( def st_distance( series: bigframes.series.Series, - other: bigframes.series.Series, + other: Union[bigframes.series.Series, shapely.Geometry], *, use_spheroid: bool = False, ) -> bigframes.series.Series: @@ -242,49 +223,25 @@ def st_distance( ... ) >>> 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), + ... Point(0.00001, 0), + ... Point(0.00003, 0), + ... Point(0.00005, 0), ... ], - ... 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_distance(s1, s2, use_spheroid=True) - 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 + 0 1.113195 + 1 2.22639 + 2 3.339585 + dtype: Float64 - We can also do intersection of each geometry and a single shapely geometry: + We can also calculate the distance 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 + >>> bbq.st_distance(s2, Point(0.00001, 0)) + 0 0.0 + 1 2.223902 + 2 4.447804 + dtype: Float64 Args: series: @@ -310,7 +267,8 @@ def st_distance( def st_intersection( - series: bigframes.series.Series, other: bigframes.series.Series + series: bigframes.series.Series, + other: Union[bigframes.series.Series, shapely.Geometry], ) -> bigframes.series.Series: """ Returns a `GEOGRAPHY` that represents the point set intersection of the two @@ -380,12 +338,12 @@ def st_intersection( 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)])])) + >>> bbq.st_intersection(s1, 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 + 1 POLYGON ((0 0, 0.99954 1, 0 1, 0 0)) + 2 LINESTRING (0 0, 0.99954 1) + 3 GEOMETRYCOLLECTION EMPTY + 4 POINT (0 1) dtype: geometry Args: diff --git a/bigframes/dtypes.py b/bigframes/dtypes.py index 47b128dae6..45fce2ae41 100644 --- a/bigframes/dtypes.py +++ b/bigframes/dtypes.py @@ -586,27 +586,29 @@ def _is_bigframes_dtype(dtype) -> bool: return False -def _infer_dtype_from_python_type(type: type) -> Dtype: +def _infer_dtype_from_python_type(type_: type) -> Dtype: if type in (datetime.timedelta, pd.Timedelta, np.timedelta64): # Must check timedelta type first. Otherwise other branchs will be evaluated to true # E.g. np.timedelta64 is a sublcass as np.integer return TIMEDELTA_DTYPE - if issubclass(type, (bool, np.bool_)): + if issubclass(type_, (bool, np.bool_)): return BOOL_DTYPE - if issubclass(type, (int, np.integer)): + if issubclass(type_, (int, np.integer)): return INT_DTYPE - if issubclass(type, (float, np.floating)): + if issubclass(type_, (float, np.floating)): return FLOAT_DTYPE - if issubclass(type, decimal.Decimal): + if issubclass(type_, decimal.Decimal): return NUMERIC_DTYPE - if issubclass(type, (str, np.str_)): + if issubclass(type_, (str, np.str_)): return STRING_DTYPE - if issubclass(type, (bytes, np.bytes_)): + if issubclass(type_, (bytes, np.bytes_)): return BYTES_DTYPE - if issubclass(type, datetime.date): + if issubclass(type_, datetime.date): return DATE_DTYPE - if issubclass(type, datetime.time): + if issubclass(type_, datetime.time): return TIME_DTYPE + if issubclass(type_, shapely.Geometry): + return GEO_DTYPE else: raise TypeError( f"No matching datatype for python type: {type}. {constants.FEEDBACK_LINK}" diff --git a/tests/system/small/bigquery/test_geo.py b/tests/system/small/bigquery/test_geo.py index c842f1c99d..207181044c 100644 --- a/tests/system/small/bigquery/test_geo.py +++ b/tests/system/small/bigquery/test_geo.py @@ -14,6 +14,7 @@ import geopandas # type: ignore import pandas as pd +import pandas.testing from shapely.geometry import ( # type: ignore GeometryCollection, LineString, @@ -23,7 +24,6 @@ import bigframes.bigquery as bbq import bigframes.geopandas -import bigframes.series def test_geo_st_area(): @@ -54,7 +54,7 @@ def test_geo_st_area(): check_dtype=False, check_index_type=False, check_exact=False, - rtol=1, + rtol=0.1, ) @@ -75,7 +75,7 @@ def test_geo_st_difference_with_geometry_objects(): geobf_s2 = bigframes.geopandas.GeoSeries(data=data2) geobf_s_result = bbq.st_difference(geobf_s1, geobf_s2).to_pandas() - expected = bigframes.series.Series( + expected = pd.Series( [ GeometryCollection([]), GeometryCollection([]), @@ -83,46 +83,45 @@ def test_geo_st_difference_with_geometry_objects(): ], index=[0, 1, 2], dtype=geopandas.array.GeometryDtype(), - ).to_pandas() - - assert geobf_s_result.dtype == "geometry" - assert expected.iloc[0].equals(geobf_s_result.iloc[0]) - assert expected.iloc[1].equals(geobf_s_result.iloc[1]) - assert expected.iloc[2].equals(geobf_s_result.iloc[2]) + ) + pandas.testing.assert_series_equal( + geobf_s_result, + expected, + check_index_type=False, + check_exact=False, + rtol=0.1, + ) def test_geo_st_difference_with_single_geometry_object(): data1 = [ - Polygon([(0, 0), (10, 0), (10, 10), (0, 0)]), - Polygon([(4, 2), (6, 2), (8, 6), (4, 2)]), + Polygon([(0, 0), (10, 0), (10, 10), (0, 10), (0, 0)]), + Polygon([(0, 1), (10, 1), (10, 9), (0, 9), (0, 1)]), Point(0, 1), ] geobf_s1 = bigframes.geopandas.GeoSeries(data=data1) geobf_s_result = bbq.st_difference( geobf_s1, - bigframes.geopandas.GeoSeries( - [ - Polygon([(0, 0), (10, 0), (10, 10), (0, 0)]), - Polygon([(1, 0), (0, 5), (0, 0), (1, 0)]), - ] - ), + Polygon([(0, 0), (10, 0), (10, 5), (0, 5), (0, 0)]), ).to_pandas() - expected = bigframes.series.Series( + expected = pd.Series( [ + Polygon([(10, 5), (10, 10), (0, 10), (0, 5), (10, 5)]), + Polygon([(10, 5), (10, 9), (0, 9), (0, 5), (10, 5)]), GeometryCollection([]), - Polygon([(4, 2), (6, 2), (8, 6), (4, 2)]), - None, ], index=[0, 1, 2], dtype=geopandas.array.GeometryDtype(), - ).to_pandas() - - assert geobf_s_result.dtype == "geometry" - assert (expected.iloc[0]).equals(geobf_s_result.iloc[0]) - assert expected.iloc[1] == geobf_s_result.iloc[1] - assert expected.iloc[2] == geobf_s_result.iloc[2] + ) + pandas.testing.assert_series_equal( + geobf_s_result, + expected, + check_index_type=False, + check_exact=False, + rtol=0.1, + ) def test_geo_st_difference_with_similar_geometry_objects(): @@ -135,16 +134,109 @@ def test_geo_st_difference_with_similar_geometry_objects(): geobf_s1 = bigframes.geopandas.GeoSeries(data=data1) geobf_s_result = bbq.st_difference(geobf_s1, geobf_s1).to_pandas() - expected = bigframes.series.Series( + expected = pd.Series( [GeometryCollection([]), GeometryCollection([]), GeometryCollection([])], index=[0, 1, 2], dtype=geopandas.array.GeometryDtype(), + ) + pandas.testing.assert_series_equal( + geobf_s_result, + expected, + check_index_type=False, + check_exact=False, + rtol=0.1, + ) + + +def test_geo_st_distance_with_geometry_objects(): + data1 = [ + # 0.00001 is approximately 1 meter. + Polygon([(0, 0), (0.00001, 0), (0.00001, 0.00001), (0, 0.00001), (0, 0)]), + Polygon( + [ + (0.00002, 0), + (0.00003, 0), + (0.00003, 0.00001), + (0.00002, 0.00001), + (0.00002, 0), + ] + ), + Point(0, 0.00002), + ] + + data2 = [ + Polygon( + [ + (0.00002, 0), + (0.00003, 0), + (0.00003, 0.00001), + (0.00002, 0.00001), + (0.00002, 0), + ] + ), + Point(0, 0.00002), + Polygon([(0, 0), (0.00001, 0), (0.00001, 0.00001), (0, 0.00001), (0, 0)]), + ] + + geobf_s1 = bigframes.geopandas.GeoSeries(data=data1) + geobf_s2 = bigframes.geopandas.GeoSeries(data=data2) + geobf_s_result = bbq.st_distance(geobf_s1, geobf_s2).to_pandas() + + expected = pd.Series( + [ + 1.112, + 2.486, + 1.112, + ], + index=[0, 1, 2], + dtype="Float64", + ) + pandas.testing.assert_series_equal( + geobf_s_result, + expected, + check_index_type=False, + check_exact=False, + rtol=0.1, + ) + + +def test_geo_st_distance_with_single_geometry_object(): + data1 = [ + # 0.00001 is approximately 1 meter. + Polygon([(0, 0), (0.00001, 0), (0.00001, 0.00001), (0, 0.00001), (0, 0)]), + Polygon( + [ + (0.00001, 0), + (0.00002, 0), + (0.00002, 0.00001), + (0.00001, 0.00001), + (0.00001, 0), + ] + ), + Point(0, 0.00002), + ] + + geobf_s1 = bigframes.geopandas.GeoSeries(data=data1) + geobf_s_result = bbq.st_distance( + geobf_s1, + Point(0, 0), ).to_pandas() - assert geobf_s_result.dtype == "geometry" - assert expected.iloc[0].equals(geobf_s_result.iloc[0]) - assert expected.iloc[1].equals(geobf_s_result.iloc[1]) - assert expected.iloc[2].equals(geobf_s_result.iloc[2]) + expected = pd.Series( + [ + 0, + 1.112, + 2.224, + ], + dtype="Float64", + ) + pandas.testing.assert_series_equal( + geobf_s_result, + expected, + check_index_type=False, + check_exact=False, + rtol=0.1, + ) def test_geo_st_intersection_with_geometry_objects(): @@ -164,7 +256,7 @@ def test_geo_st_intersection_with_geometry_objects(): geobf_s2 = bigframes.geopandas.GeoSeries(data=data2) geobf_s_result = bbq.st_intersection(geobf_s1, geobf_s2).to_pandas() - expected = bigframes.series.Series( + expected = pd.Series( [ Polygon([(0, 0), (10, 0), (10, 10), (0, 0)]), Polygon([(0, 0), (1, 1), (0, 1), (0, 0)]), @@ -172,46 +264,45 @@ def test_geo_st_intersection_with_geometry_objects(): ], index=[0, 1, 2], dtype=geopandas.array.GeometryDtype(), - ).to_pandas() - - assert geobf_s_result.dtype == "geometry" - assert expected.iloc[0].equals(geobf_s_result.iloc[0]) - assert expected.iloc[1].equals(geobf_s_result.iloc[1]) - assert expected.iloc[2].equals(geobf_s_result.iloc[2]) + ) + pandas.testing.assert_series_equal( + geobf_s_result, + expected, + check_index_type=False, + check_exact=False, + rtol=0.1, + ) def test_geo_st_intersection_with_single_geometry_object(): data1 = [ - Polygon([(0, 0), (10, 0), (10, 10), (0, 0)]), - Polygon([(4, 2), (6, 2), (8, 6), (4, 2)]), + Polygon([(0, 0), (10, 0), (10, 10), (0, 10), (0, 0)]), + Polygon([(0, 1), (10, 1), (10, 9), (0, 9), (0, 1)]), Point(0, 1), ] geobf_s1 = bigframes.geopandas.GeoSeries(data=data1) geobf_s_result = bbq.st_intersection( geobf_s1, - bigframes.geopandas.GeoSeries( - [ - Polygon([(0, 0), (10, 0), (10, 10), (0, 0)]), - Polygon([(1, 0), (0, 5), (0, 0), (1, 0)]), - ] - ), + Polygon([(0, 0), (10, 0), (10, 5), (0, 5), (0, 0)]), ).to_pandas() - expected = bigframes.series.Series( + expected = pd.Series( [ - Polygon([(0, 0), (10, 0), (10, 10), (0, 0)]), - GeometryCollection([]), - None, + Polygon([(0, 0), (10, 0), (10, 5), (0, 5), (0, 0)]), + Polygon([(0, 1), (10, 1), (10, 5), (0, 5), (0, 1)]), + Point(0, 1), ], index=[0, 1, 2], dtype=geopandas.array.GeometryDtype(), - ).to_pandas() - - assert geobf_s_result.dtype == "geometry" - assert (expected.iloc[0]).equals(geobf_s_result.iloc[0]) - assert expected.iloc[1] == geobf_s_result.iloc[1] - assert expected.iloc[2] == geobf_s_result.iloc[2] + ) + pandas.testing.assert_series_equal( + geobf_s_result, + expected, + check_index_type=False, + check_exact=False, + rtol=0.1, + ) def test_geo_st_intersection_with_similar_geometry_objects(): @@ -224,7 +315,7 @@ def test_geo_st_intersection_with_similar_geometry_objects(): geobf_s1 = bigframes.geopandas.GeoSeries(data=data1) geobf_s_result = bbq.st_intersection(geobf_s1, geobf_s1).to_pandas() - expected = bigframes.series.Series( + expected = pd.Series( [ Polygon([(0, 0), (10, 0), (10, 10), (0, 0)]), Polygon([(0, 0), (1, 1), (0, 1)]), @@ -232,9 +323,11 @@ def test_geo_st_intersection_with_similar_geometry_objects(): ], index=[0, 1, 2], dtype=geopandas.array.GeometryDtype(), - ).to_pandas() - - assert geobf_s_result.dtype == "geometry" - assert expected.iloc[0].equals(geobf_s_result.iloc[0]) - assert expected.iloc[1].equals(geobf_s_result.iloc[1]) - assert expected.iloc[2].equals(geobf_s_result.iloc[2]) + ) + pandas.testing.assert_series_equal( + geobf_s_result, + expected, + check_index_type=False, + check_exact=False, + rtol=0.1, + ) diff --git a/tests/system/small/geopandas/test_geoseries.py b/tests/system/small/geopandas/test_geoseries.py index 18f3ff2675..d5c571b303 100644 --- a/tests/system/small/geopandas/test_geoseries.py +++ b/tests/system/small/geopandas/test_geoseries.py @@ -29,6 +29,7 @@ ) import bigframes.geopandas +import bigframes.pandas import bigframes.series from tests.system.utils import assert_series_equal @@ -95,6 +96,34 @@ def test_geo_area_not_supported(): bf_series.area +def test_geo_distance_not_supported(): + s1 = bigframes.pandas.Series( + [ + Polygon([(0, 0), (1, 1), (0, 1)]), + Polygon([(10, 0), (10, 5), (0, 0)]), + Polygon([(0, 0), (2, 2), (2, 0)]), + LineString([(0, 0), (1, 1), (0, 1)]), + Point(0, 1), + ], + dtype=GeometryDtype(), + ) + s2 = bigframes.pandas.Series( + [ + Polygon([(0, 0), (1, 1), (0, 1)]), + Polygon([(10, 0), (10, 5), (0, 0)]), + Polygon([(0, 0), (2, 2), (2, 0)]), + LineString([(0, 0), (1, 1), (0, 1)]), + Point(0, 1), + ], + dtype=GeometryDtype(), + ) + with pytest.raises( + NotImplementedError, + match=re.escape("GeoSeries.distance is not supported."), + ): + s1.geo.distance(s2) + + def test_geo_from_xy(): x = [2.5, 5, -3.0] y = [0.5, 1, 1.5] diff --git a/tests/unit/core/test_dtypes.py b/tests/unit/core/test_dtypes.py index 3d420de51f..b77ed10658 100644 --- a/tests/unit/core/test_dtypes.py +++ b/tests/unit/core/test_dtypes.py @@ -20,6 +20,7 @@ import pandas as pd import pyarrow as pa # type: ignore import pytest +import shapely import bigframes.core.compile.ibis_types import bigframes.dtypes @@ -224,6 +225,22 @@ def test_bigframes_string_dtype_converts(ibis_dtype, bigframes_dtype_str): assert result == ibis_dtype +@pytest.mark.parametrize( + ["python_type", "expected_dtype"], + [ + (bool, bigframes.dtypes.BOOL_DTYPE), + (int, bigframes.dtypes.INT_DTYPE), + (str, bigframes.dtypes.STRING_DTYPE), + (shapely.Point, bigframes.dtypes.GEO_DTYPE), + (shapely.Polygon, bigframes.dtypes.GEO_DTYPE), + (shapely.Geometry, bigframes.dtypes.GEO_DTYPE), + ], +) +def test_bigframes_type_supports_python_types(python_type, expected_dtype): + got_dtype = bigframes.dtypes.bigframes_type(python_type) + assert got_dtype == expected_dtype + + def test_unsupported_dtype_raises_unexpected_datatype(): """Incompatible dtypes should fail when passed into BigQuery DataFrames""" with pytest.raises(ValueError, match="Datatype has no ibis type mapping"): From 14841efb4d5e85fb73dbb6a9d517fa437e4e5f68 Mon Sep 17 00:00:00 2001 From: Tim Swena Date: Mon, 21 Apr 2025 17:03:52 -0500 Subject: [PATCH 04/11] add tests --- tests/unit/core/test_dtypes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/core/test_dtypes.py b/tests/unit/core/test_dtypes.py index b77ed10658..bbeac3602b 100644 --- a/tests/unit/core/test_dtypes.py +++ b/tests/unit/core/test_dtypes.py @@ -20,7 +20,7 @@ import pandas as pd import pyarrow as pa # type: ignore import pytest -import shapely +import shapely # type: ignore import bigframes.core.compile.ibis_types import bigframes.dtypes From 4a26fbdb3b2e7612ba9d0d471b8f30da97f7fdf9 Mon Sep 17 00:00:00 2001 From: Tim Swena Date: Mon, 21 Apr 2025 17:06:01 -0500 Subject: [PATCH 05/11] type checks --- bigframes/bigquery/_operations/geo.py | 2 +- tests/system/small/geopandas/test_geoseries.py | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/bigframes/bigquery/_operations/geo.py b/bigframes/bigquery/_operations/geo.py index f79c23647b..8458e4f20f 100644 --- a/bigframes/bigquery/_operations/geo.py +++ b/bigframes/bigquery/_operations/geo.py @@ -16,7 +16,7 @@ from typing import Union -import shapely +import shapely # type: ignore from bigframes import operations as ops import bigframes.dtypes diff --git a/tests/system/small/geopandas/test_geoseries.py b/tests/system/small/geopandas/test_geoseries.py index d5c571b303..ae99fd6fc2 100644 --- a/tests/system/small/geopandas/test_geoseries.py +++ b/tests/system/small/geopandas/test_geoseries.py @@ -107,15 +107,14 @@ def test_geo_distance_not_supported(): ], dtype=GeometryDtype(), ) - s2 = bigframes.pandas.Series( + s2 = bigframes.geopandas.GeoSeries( [ Polygon([(0, 0), (1, 1), (0, 1)]), Polygon([(10, 0), (10, 5), (0, 0)]), Polygon([(0, 0), (2, 2), (2, 0)]), LineString([(0, 0), (1, 1), (0, 1)]), Point(0, 1), - ], - dtype=GeometryDtype(), + ] ) with pytest.raises( NotImplementedError, From 645d7ba7bf23b1bbd040911e73c751f0e5472241 Mon Sep 17 00:00:00 2001 From: Tim Swena Date: Tue, 22 Apr 2025 09:25:54 -0500 Subject: [PATCH 06/11] make sure shapely.Point is available --- setup.py | 2 +- testing/constraints-3.9.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index edc77e11b6..1fe7006860 100644 --- a/setup.py +++ b/setup.py @@ -53,7 +53,7 @@ "pyarrow >=15.0.2", "pydata-google-auth >=1.8.2", "requests >=2.27.1", - "shapely >=1.8.5", + "shapely >=2.0.0", "sqlglot >=23.6.3", "tabulate >=0.9", "ipywidgets >=7.7.1", diff --git a/testing/constraints-3.9.txt b/testing/constraints-3.9.txt index dff245d176..b0537cd035 100644 --- a/testing/constraints-3.9.txt +++ b/testing/constraints-3.9.txt @@ -19,7 +19,7 @@ pyarrow==15.0.2 pydata-google-auth==1.8.2 requests==2.27.1 scikit-learn==1.2.2 -shapely==1.8.5 +shapely==2.0.0 sqlglot==23.6.3 tabulate==0.9 ipywidgets==7.7.1 From 07f6d6e648372091702b3a49192f6467ee630f34 Mon Sep 17 00:00:00 2001 From: Tim Swena Date: Tue, 22 Apr 2025 09:40:18 -0500 Subject: [PATCH 07/11] fix docstrings, add null row test --- bigframes/bigquery/_operations/geo.py | 51 ++++++++++++++++--------- tests/system/small/bigquery/test_geo.py | 6 ++- 2 files changed, 39 insertions(+), 18 deletions(-) diff --git a/bigframes/bigquery/_operations/geo.py b/bigframes/bigquery/_operations/geo.py index 8458e4f20f..ecac60f003 100644 --- a/bigframes/bigquery/_operations/geo.py +++ b/bigframes/bigquery/_operations/geo.py @@ -19,7 +19,6 @@ import shapely # type: ignore from bigframes import operations as ops -import bigframes.dtypes import bigframes.geopandas import bigframes.series @@ -29,7 +28,9 @@ """ -def st_area(series: bigframes.series.Series) -> bigframes.series.Series: +def st_area( + series: Union[bigframes.series.Series, bigframes.geopandas.GeoSeries], +) -> bigframes.series.Series: """ Returns the area in square meters covered by the polygons in the input `GEOGRAPHY`. @@ -89,6 +90,10 @@ def st_area(series: bigframes.series.Series) -> bigframes.series.Series: 4 0.0 dtype: Float64 + Args: + series (bigframes.pandas.Series | bigframes.geopandas.GeoSereies): + A series containing geography objects. + Returns: bigframes.pandas.Series: Series of float representing the areas. @@ -99,8 +104,10 @@ def st_area(series: bigframes.series.Series) -> bigframes.series.Series: def st_difference( - series: bigframes.series.Series, - other: Union[bigframes.series.Series, shapely.Geometry], + series: Union[bigframes.series.Series, bigframes.geopandas.GeoSeries], + other: Union[ + bigframes.series.Series, bigframes.geopandas.GeoSeries, shapely.Geometry + ], ) -> bigframes.series.Series: """ Returns a `GEOGRAPHY` that represents the point set difference of @@ -183,8 +190,11 @@ def st_difference( dtype: geometry Args: - other (bigframes.series.Series or geometric object): - The GeoSeries (elementwise) or geometric object to find the difference to. + series (bigframes.pandas.Series | bigframes.geopandas.GeoSereies): + A series containing geography objects. + other (bigframes.pandas.Series | bigframes.geopandas.GeoSeries | shapely.Geometry): + The series or geometric object to subtract from the geography + objects in ``series``. Returns: bigframes.series.Series: @@ -195,8 +205,10 @@ def st_difference( def st_distance( - series: bigframes.series.Series, - other: Union[bigframes.series.Series, shapely.Geometry], + series: Union[bigframes.series.Series, bigframes.geopandas.GeoSeries], + other: Union[ + bigframes.series.Series, bigframes.geopandas.GeoSeries, shapely.Geometry + ], *, use_spheroid: bool = False, ) -> bigframes.series.Series: @@ -244,10 +256,11 @@ def st_distance( dtype: Float64 Args: - series: - A ``GEOGRAPHY`` type Series or GeoSeries. - other: - A ``GEOGRAPHY`` type Series or GeoSeries. + series (bigframes.pandas.Series | bigframes.geopandas.GeoSereies): + A series containing geography objects. + other (bigframes.pandas.Series | bigframes.geopandas.GeoSeries | shapely.Geometry): + The series or geometric object to calculate the distance in meters + to from the geography objects in ``series``. use_spheroid (optional, default ``False``): Determines how this function measures distance. If ``use_spheroid`` is False, the function measures distance on the surface of a perfect @@ -267,8 +280,10 @@ def st_distance( def st_intersection( - series: bigframes.series.Series, - other: Union[bigframes.series.Series, shapely.Geometry], + series: Union[bigframes.series.Series, bigframes.geopandas.GeoSeries], + other: Union[ + bigframes.series.Series, bigframes.geopandas.GeoSeries, shapely.Geometry + ], ) -> bigframes.series.Series: """ Returns a `GEOGRAPHY` that represents the point set intersection of the two @@ -347,9 +362,11 @@ def st_intersection( dtype: geometry Args: - other (GeoSeries or geometric object): - The Geoseries (elementwise) or geometric object to find the - intersection with. + series (bigframes.pandas.Series | bigframes.geopandas.GeoSereies): + A series containing geography objects. + other (bigframes.pandas.Series | bigframes.geopandas.GeoSeries | shapely.Geometry): + The series or geometric object to intersect with the geography + objects in ``series``. Returns: bigframes.geopandas.GeoSeries: diff --git a/tests/system/small/bigquery/test_geo.py b/tests/system/small/bigquery/test_geo.py index 207181044c..fa2c522109 100644 --- a/tests/system/small/bigquery/test_geo.py +++ b/tests/system/small/bigquery/test_geo.py @@ -176,6 +176,9 @@ def test_geo_st_distance_with_geometry_objects(): ), Point(0, 0.00002), Polygon([(0, 0), (0.00001, 0), (0.00001, 0.00001), (0, 0.00001), (0, 0)]), + Point( + 1, 1 + ), # No matching row in data1, so this will be NULL after the call to distance. ] geobf_s1 = bigframes.geopandas.GeoSeries(data=data1) @@ -187,8 +190,9 @@ def test_geo_st_distance_with_geometry_objects(): 1.112, 2.486, 1.112, + None, ], - index=[0, 1, 2], + index=[0, 1, 2, 3], dtype="Float64", ) pandas.testing.assert_series_equal( From 9efd6f0b38d738e2c468512dbe499dd49d19182c Mon Sep 17 00:00:00 2001 From: Tim Swena Date: Tue, 22 Apr 2025 09:42:58 -0500 Subject: [PATCH 08/11] GeoSereies typo --- bigframes/bigquery/_operations/geo.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/bigframes/bigquery/_operations/geo.py b/bigframes/bigquery/_operations/geo.py index ecac60f003..262dca6d6b 100644 --- a/bigframes/bigquery/_operations/geo.py +++ b/bigframes/bigquery/_operations/geo.py @@ -91,7 +91,7 @@ def st_area( dtype: Float64 Args: - series (bigframes.pandas.Series | bigframes.geopandas.GeoSereies): + series (bigframes.pandas.Series | bigframes.geopandas.GeoSeries): A series containing geography objects. Returns: @@ -190,7 +190,7 @@ def st_difference( dtype: geometry Args: - series (bigframes.pandas.Series | bigframes.geopandas.GeoSereies): + series (bigframes.pandas.Series | bigframes.geopandas.GeoSeries): A series containing geography objects. other (bigframes.pandas.Series | bigframes.geopandas.GeoSeries | shapely.Geometry): The series or geometric object to subtract from the geography @@ -256,7 +256,7 @@ def st_distance( dtype: Float64 Args: - series (bigframes.pandas.Series | bigframes.geopandas.GeoSereies): + series (bigframes.pandas.Series | bigframes.geopandas.GeoSeries): A series containing geography objects. other (bigframes.pandas.Series | bigframes.geopandas.GeoSeries | shapely.Geometry): The series or geometric object to calculate the distance in meters @@ -362,7 +362,7 @@ def st_intersection( dtype: geometry Args: - series (bigframes.pandas.Series | bigframes.geopandas.GeoSereies): + series (bigframes.pandas.Series | bigframes.geopandas.GeoSeries): A series containing geography objects. other (bigframes.pandas.Series | bigframes.geopandas.GeoSeries | shapely.Geometry): The series or geometric object to intersect with the geography From 00752273310725bc53c4bfa004ea03c22dfb47cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Swe=C3=B1a=20=28Swast=29?= Date: Tue, 22 Apr 2025 09:51:18 -0500 Subject: [PATCH 09/11] Update bigframes/dtypes.py --- bigframes/dtypes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bigframes/dtypes.py b/bigframes/dtypes.py index 45fce2ae41..148fe0aa55 100644 --- a/bigframes/dtypes.py +++ b/bigframes/dtypes.py @@ -611,7 +611,7 @@ def _infer_dtype_from_python_type(type_: type) -> Dtype: return GEO_DTYPE else: raise TypeError( - f"No matching datatype for python type: {type}. {constants.FEEDBACK_LINK}" + f"No matching datatype for python type: {type_}. {constants.FEEDBACK_LINK}" ) From 43b3a785214eec0d5ff4f5acd555d720d58e96a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Swe=C3=B1a=20=28Swast=29?= Date: Tue, 22 Apr 2025 09:51:51 -0500 Subject: [PATCH 10/11] Update bigframes/dtypes.py --- bigframes/dtypes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bigframes/dtypes.py b/bigframes/dtypes.py index 148fe0aa55..de6c331043 100644 --- a/bigframes/dtypes.py +++ b/bigframes/dtypes.py @@ -587,7 +587,7 @@ def _is_bigframes_dtype(dtype) -> bool: def _infer_dtype_from_python_type(type_: type) -> Dtype: - if type in (datetime.timedelta, pd.Timedelta, np.timedelta64): + if type_ in (datetime.timedelta, pd.Timedelta, np.timedelta64): # Must check timedelta type first. Otherwise other branchs will be evaluated to true # E.g. np.timedelta64 is a sublcass as np.integer return TIMEDELTA_DTYPE From 69d4228eff34e9fa48ae60314529f758a458bb68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Swe=C3=B1a=20=28Swast=29?= Date: Tue, 22 Apr 2025 09:53:33 -0500 Subject: [PATCH 11/11] Update third_party/bigframes_vendored/geopandas/geoseries.py --- third_party/bigframes_vendored/geopandas/geoseries.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/third_party/bigframes_vendored/geopandas/geoseries.py b/third_party/bigframes_vendored/geopandas/geoseries.py index 12374a14c6..613a929c04 100644 --- a/third_party/bigframes_vendored/geopandas/geoseries.py +++ b/third_party/bigframes_vendored/geopandas/geoseries.py @@ -381,7 +381,7 @@ def distance(self: GeoSeries, other: GeoSeries) -> bigframes.series.Series: instead to return the shorted distance between two ``GEOGRAPHY`` objects in meters. - In GeoPandas, this returns a Series of the distancies between each + In GeoPandas, this returns a Series of the distances between each aligned geometry in the expressed in the units of the CRS. Args: