Skip to content

Commit e39ee3b

Browse files
authored
deps: support a shapely versions 1.8.5+ (#1621)
1 parent 637e860 commit e39ee3b

File tree

10 files changed

+74
-49
lines changed

10 files changed

+74
-49
lines changed

bigframes/core/sql.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
import math
2424
from typing import cast, Collection, Iterable, Mapping, Optional, TYPE_CHECKING, Union
2525

26-
import shapely # type: ignore
26+
import shapely.geometry.base # type: ignore
2727

2828
import bigframes.core.compile.googlesql as googlesql
2929

@@ -33,9 +33,19 @@
3333
import bigframes.core.ordering
3434

3535

36+
# shapely.wkt.dumps was moved to shapely.io.to_wkt in 2.0.
37+
try:
38+
from shapely.io import to_wkt # type: ignore
39+
except ImportError:
40+
from shapely.wkt import dumps # type: ignore
41+
42+
to_wkt = dumps
43+
44+
3645
### Writing SQL Values (literals, column references, table references, etc.)
3746
def simple_literal(value: bytes | str | int | bool | float | datetime.datetime | None):
3847
"""Return quoted input string."""
48+
3949
# https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical#literals
4050
if value is None:
4151
return "NULL"
@@ -65,8 +75,8 @@ def simple_literal(value: bytes | str | int | bool | float | datetime.datetime |
6575
return f"DATE('{value.isoformat()}')"
6676
elif isinstance(value, datetime.time):
6777
return f"TIME(DATETIME('1970-01-01 {value.isoformat()}'))"
68-
elif isinstance(value, shapely.Geometry):
69-
return f"ST_GEOGFROMTEXT({simple_literal(shapely.to_wkt(value))})"
78+
elif isinstance(value, shapely.geometry.base.BaseGeometry):
79+
return f"ST_GEOGFROMTEXT({simple_literal(to_wkt(value))})"
7080
elif isinstance(value, decimal.Decimal):
7181
# TODO: disambiguate BIGNUMERIC based on scale and/or precision
7282
return f"CAST('{str(value)}' AS NUMERIC)"

bigframes/dtypes.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
import numpy as np
2929
import pandas as pd
3030
import pyarrow as pa
31-
import shapely # type: ignore
31+
import shapely.geometry # type: ignore
3232

3333
# Type hints for Pandas dtypes supported by BigQuery DataFrame
3434
Dtype = Union[
@@ -506,7 +506,7 @@ def bigframes_dtype_to_literal(
506506
if isinstance(bigframes_dtype, pd.StringDtype):
507507
return "string"
508508
if isinstance(bigframes_dtype, gpd.array.GeometryDtype):
509-
return shapely.Point((0, 0))
509+
return shapely.geometry.Point((0, 0))
510510

511511
raise TypeError(
512512
f"No literal conversion for {bigframes_dtype}. {constants.FEEDBACK_LINK}"

bigframes/session/_io/pandas.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,10 @@ def arrow_to_pandas(
7878

7979
if dtype == geopandas.array.GeometryDtype():
8080
series = geopandas.GeoSeries.from_wkt(
81-
column,
81+
# Use `to_pylist()` is a workaround for TypeError: object of type
82+
# 'pyarrow.lib.StringScalar' has no len() on older pyarrow,
83+
# geopandas, shapely combinations.
84+
column.to_pylist(),
8285
# BigQuery geography type is based on the WGS84 reference ellipsoid.
8386
crs="EPSG:4326",
8487
)

setup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
"pyarrow >=15.0.2",
5454
"pydata-google-auth >=1.8.2",
5555
"requests >=2.27.1",
56+
"shapely >=1.8.5",
5657
"sqlglot >=23.6.3",
5758
"tabulate >=0.9",
5859
"ipywidgets >=7.7.1",

testing/constraints-3.9.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ pyarrow==15.0.2
1919
pydata-google-auth==1.8.2
2020
requests==2.27.1
2121
scikit-learn==1.2.2
22+
shapely==1.8.5
2223
sqlglot==23.6.3
2324
tabulate==0.9
2425
ipywidgets==7.7.1

tests/system/small/test_series.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
import pandas as pd
2525
import pyarrow as pa # type: ignore
2626
import pytest
27-
import shapely # type: ignore
27+
import shapely.geometry # type: ignore
2828

2929
import bigframes.dtypes as dtypes
3030
import bigframes.features
@@ -229,7 +229,11 @@ def test_series_construct_from_list_escaped_strings():
229229

230230
def test_series_construct_geodata():
231231
pd_series = pd.Series(
232-
[shapely.Point(1, 1), shapely.Point(2, 2), shapely.Point(3, 3)],
232+
[
233+
shapely.geometry.Point(1, 1),
234+
shapely.geometry.Point(2, 2),
235+
shapely.geometry.Point(3, 3),
236+
],
233237
dtype=gpd.array.GeometryDtype(),
234238
)
235239

tests/unit/core/test_sql.py

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,16 @@
1414

1515
import datetime
1616
import decimal
17+
import re
1718

1819
import pytest
19-
import shapely # type: ignore
20+
import shapely.geometry # type: ignore
2021

2122
from bigframes.core import sql
2223

2324

2425
@pytest.mark.parametrize(
25-
("value", "expected"),
26+
("value", "expected_pattern"),
2627
(
2728
# Try to have some literals for each scalar data type:
2829
# https://cloud.google.com/bigquery/docs/reference/standard-sql/data-types
@@ -32,44 +33,44 @@
3233
(False, "False"),
3334
(
3435
b"\x01\x02\x03ABC",
35-
r"b'\x01\x02\x03ABC'",
36+
re.escape(r"b'\x01\x02\x03ABC'"),
3637
),
3738
(
3839
datetime.date(2025, 1, 1),
39-
"DATE('2025-01-01')",
40+
re.escape("DATE('2025-01-01')"),
4041
),
4142
(
4243
datetime.datetime(2025, 1, 2, 3, 45, 6, 789123),
43-
"DATETIME('2025-01-02T03:45:06.789123')",
44+
re.escape("DATETIME('2025-01-02T03:45:06.789123')"),
4445
),
4546
(
46-
shapely.Point(0, 1),
47-
"ST_GEOGFROMTEXT('POINT (0 1)')",
47+
shapely.geometry.Point(0, 1),
48+
r"ST_GEOGFROMTEXT\('POINT \(0[.]?0* 1[.]?0*\)'\)",
4849
),
4950
# TODO: INTERVAL type (e.g. from dateutil.relativedelta)
5051
# TODO: JSON type (TBD what Python object that would correspond to)
51-
(123, "123"),
52-
(decimal.Decimal("123.75"), "CAST('123.75' AS NUMERIC)"),
52+
(123, re.escape("123")),
53+
(decimal.Decimal("123.75"), re.escape("CAST('123.75' AS NUMERIC)")),
5354
# TODO: support BIGNUMERIC by looking at precision/scale of the DECIMAL
54-
(123.75, "123.75"),
55+
(123.75, re.escape("123.75")),
5556
# TODO: support RANGE type
56-
("abc", "'abc'"),
57+
("abc", re.escape("'abc'")),
5758
# TODO: support STRUCT type (possibly another method?)
5859
(
5960
datetime.time(12, 34, 56, 789123),
60-
"TIME(DATETIME('1970-01-01 12:34:56.789123'))",
61+
re.escape("TIME(DATETIME('1970-01-01 12:34:56.789123'))"),
6162
),
6263
(
6364
datetime.datetime(
6465
2025, 1, 2, 3, 45, 6, 789123, tzinfo=datetime.timezone.utc
6566
),
66-
"TIMESTAMP('2025-01-02T03:45:06.789123+00:00')",
67+
re.escape("TIMESTAMP('2025-01-02T03:45:06.789123+00:00')"),
6768
),
6869
),
6970
)
70-
def test_simple_literal(value, expected):
71+
def test_simple_literal(value, expected_pattern):
7172
got = sql.simple_literal(value)
72-
assert got == expected
73+
assert re.match(expected_pattern, got) is not None
7374

7475

7576
def test_create_vector_search_sql_simple():

third_party/bigframes_vendored/geopandas/geoseries.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,11 +45,11 @@ def x(self) -> bigframes.series.Series:
4545
4646
>>> import bigframes.pandas as bpd
4747
>>> import geopandas.array
48-
>>> import shapely
48+
>>> import shapely.geometry
4949
>>> bpd.options.display.progress_bar = None
5050
5151
>>> series = bpd.Series(
52-
... [shapely.Point(1, 2), shapely.Point(2, 3), shapely.Point(3, 4)],
52+
... [shapely.geometry.Point(1, 2), shapely.geometry.Point(2, 3), shapely.geometry.Point(3, 4)],
5353
... dtype=geopandas.array.GeometryDtype()
5454
... )
5555
>>> series.geo.x
@@ -72,11 +72,11 @@ def y(self) -> bigframes.series.Series:
7272
7373
>>> import bigframes.pandas as bpd
7474
>>> import geopandas.array
75-
>>> import shapely
75+
>>> import shapely.geometry
7676
>>> bpd.options.display.progress_bar = None
7777
7878
>>> series = bpd.Series(
79-
... [shapely.Point(1, 2), shapely.Point(2, 3), shapely.Point(3, 4)],
79+
... [shapely.geometry.Point(1, 2), shapely.geometry.Point(2, 3), shapely.geometry.Point(3, 4)],
8080
... dtype=geopandas.array.GeometryDtype()
8181
... )
8282
>>> series.geo.y
@@ -101,7 +101,7 @@ def boundary(self) -> bigframes.geopandas.GeoSeries:
101101
102102
>>> import bigframes.pandas as bpd
103103
>>> import geopandas.array
104-
>>> import shapely
104+
>>> import shapely.geometry
105105
>>> bpd.options.display.progress_bar = None
106106
107107
>>> from shapely.geometry import Polygon, LineString, Point

third_party/bigframes_vendored/ibis/expr/datatypes/value.py

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -312,15 +312,16 @@ def normalize(typ, value):
312312
)
313313
return frozendict({k: normalize(t, value[k]) for k, t in dtype.items()})
314314
elif dtype.is_geospatial():
315-
import shapely as shp
315+
import shapely
316+
import shapely.geometry
316317

317318
if isinstance(value, (tuple, list)):
318319
if dtype.is_point():
319-
return shp.Point(value)
320+
return shapely.geometry.Point(value)
320321
elif dtype.is_linestring():
321-
return shp.LineString(value)
322+
return shapely.geometry.LineString(value)
322323
elif dtype.is_polygon():
323-
return shp.Polygon(
324+
return shapely.geometry.Polygon(
324325
toolz.concat(
325326
map(
326327
attrgetter("coords"),
@@ -329,19 +330,23 @@ def normalize(typ, value):
329330
)
330331
)
331332
elif dtype.is_multipoint():
332-
return shp.MultiPoint(tuple(map(partial(normalize, dt.point), value)))
333+
return shapely.geometry.MultiPoint(
334+
tuple(map(partial(normalize, dt.point), value))
335+
)
333336
elif dtype.is_multilinestring():
334-
return shp.MultiLineString(
337+
return shapely.geometry.MultiLineString(
335338
tuple(map(partial(normalize, dt.linestring), value))
336339
)
337340
elif dtype.is_multipolygon():
338-
return shp.MultiPolygon(map(partial(normalize, dt.polygon), value))
341+
return shapely.geometry.MultiPolygon(
342+
map(partial(normalize, dt.polygon), value)
343+
)
339344
else:
340345
raise IbisTypeError(f"Unsupported geospatial type: {dtype}")
341-
elif isinstance(value, shp.geometry.base.BaseGeometry):
346+
elif isinstance(value, shapely.geometry.base.BaseGeometry):
342347
return value
343348
else:
344-
return shp.from_wkt(value)
349+
return shapely.from_wkt(value)
345350
elif dtype.is_date():
346351
return normalize_datetime(value).date()
347352
elif dtype.is_time():

third_party/bigframes_vendored/ibis/expr/types/geospatial.py

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ def contains(self, right: GeoSpatialValue) -> ir.BooleanValue:
135135
>>> ibis.options.interactive = True
136136
>>> import shapely
137137
>>> t = ibis.examples.zones.fetch()
138-
>>> p = shapely.Point(935996.821, 191376.75) # centroid for zone 1
138+
>>> p = shapely.geometry.Point(935996.821, 191376.75) # centroid for zone 1
139139
>>> plit = ibis.literal(p, "geometry")
140140
>>> t.geom.contains(plit).name("contains")
141141
┏━━━━━━━━━━┓
@@ -197,7 +197,7 @@ def covers(self, right: GeoSpatialValue) -> ir.BooleanValue:
197197
198198
Polygon area center in zone 1
199199
200-
>>> z1_ctr_buff = shapely.Point(935996.821, 191376.75).buffer(10)
200+
>>> z1_ctr_buff = shapely.geometry.Point(935996.821, 191376.75).buffer(10)
201201
>>> z1_ctr_buff_lit = ibis.literal(z1_ctr_buff, "geometry")
202202
>>> t.geom.covers(z1_ctr_buff_lit).name("covers")
203203
┏━━━━━━━━━┓
@@ -242,7 +242,7 @@ def covered_by(self, right: GeoSpatialValue) -> ir.BooleanValue:
242242
243243
Polygon area center in zone 1
244244
245-
>>> pol_big = shapely.Point(935996.821, 191376.75).buffer(10000)
245+
>>> pol_big = shapely.geometry.Point(935996.821, 191376.75).buffer(10000)
246246
>>> pol_big_lit = ibis.literal(pol_big, "geometry")
247247
>>> t.geom.covered_by(pol_big_lit).name("covered_by")
248248
┏━━━━━━━━━━━━┓
@@ -262,7 +262,7 @@ def covered_by(self, right: GeoSpatialValue) -> ir.BooleanValue:
262262
│ False │
263263
│ … │
264264
└────────────┘
265-
>>> pol_small = shapely.Point(935996.821, 191376.75).buffer(100)
265+
>>> pol_small = shapely.geometry.Point(935996.821, 191376.75).buffer(100)
266266
>>> pol_small_lit = ibis.literal(pol_small, "geometry")
267267
>>> t.geom.covered_by(pol_small_lit).name("covered_by")
268268
┏━━━━━━━━━━━━┓
@@ -387,7 +387,7 @@ def disjoint(self, right: GeoSpatialValue) -> ir.BooleanValue:
387387
>>> ibis.options.interactive = True
388388
>>> import shapely
389389
>>> t = ibis.examples.zones.fetch()
390-
>>> p = shapely.Point(935996.821, 191376.75) # zone 1 centroid
390+
>>> p = shapely.geometry.Point(935996.821, 191376.75) # zone 1 centroid
391391
>>> plit = ibis.literal(p, "geometry")
392392
>>> t.geom.disjoint(plit).name("disjoint")
393393
┏━━━━━━━━━━┓
@@ -435,7 +435,7 @@ def d_within(
435435
>>> ibis.options.interactive = True
436436
>>> import shapely
437437
>>> t = ibis.examples.zones.fetch()
438-
>>> penn_station = shapely.Point(986345.399, 211974.446)
438+
>>> penn_station = shapely.geometry.Point(986345.399, 211974.446)
439439
>>> penn_lit = ibis.literal(penn_station, "geometry")
440440
441441
Check zones within 1000ft of Penn Station centroid
@@ -578,7 +578,7 @@ def intersects(self, right: GeoSpatialValue) -> ir.BooleanValue:
578578
>>> ibis.options.interactive = True
579579
>>> import shapely
580580
>>> t = ibis.examples.zones.fetch()
581-
>>> p = shapely.Point(935996.821, 191376.75) # zone 1 centroid
581+
>>> p = shapely.geometry.Point(935996.821, 191376.75) # zone 1 centroid
582582
>>> plit = ibis.literal(p, "geometry")
583583
>>> t.geom.intersects(plit).name("intersects")
584584
┏━━━━━━━━━━━━┓
@@ -675,7 +675,7 @@ def overlaps(self, right: GeoSpatialValue) -> ir.BooleanValue:
675675
676676
Polygon center in an edge point of zone 1
677677
678-
>>> p_edge_buffer = shapely.Point(933100.918, 192536.086).buffer(100)
678+
>>> p_edge_buffer = shapely.geometry.Point(933100.918, 192536.086).buffer(100)
679679
>>> buff_lit = ibis.literal(p_edge_buffer, "geometry")
680680
>>> t.geom.overlaps(buff_lit).name("overlaps")
681681
┏━━━━━━━━━━┓
@@ -720,7 +720,7 @@ def touches(self, right: GeoSpatialValue) -> ir.BooleanValue:
720720
721721
Edge point of zone 1
722722
723-
>>> p_edge = shapely.Point(933100.9183527103, 192536.08569720192)
723+
>>> p_edge = shapely.geometry.Point(933100.9183527103, 192536.08569720192)
724724
>>> p_edge_lit = ibis.literal(p_edge, "geometry")
725725
>>> t.geom.touches(p_edge_lit).name("touches")
726726
┏━━━━━━━━━┓
@@ -765,7 +765,7 @@ def distance(self, right: GeoSpatialValue) -> ir.FloatingValue:
765765
766766
Penn station zone centroid
767767
768-
>>> penn_station = shapely.Point(986345.399, 211974.446)
768+
>>> penn_station = shapely.geometry.Point(986345.399, 211974.446)
769769
>>> penn_lit = ibis.literal(penn_station, "geometry")
770770
>>> t.geom.distance(penn_lit).name("distance_penn")
771771
┏━━━━━━━━━━━━━━━┓
@@ -886,7 +886,7 @@ def union(self, right: GeoSpatialValue) -> GeoSpatialValue:
886886
887887
Penn station zone centroid
888888
889-
>>> penn_station = shapely.Point(986345.399, 211974.446)
889+
>>> penn_station = shapely.geometry.Point(986345.399, 211974.446)
890890
>>> penn_lit = ibis.literal(penn_station, "geometry")
891891
>>> t.geom.centroid().union(penn_lit).name("union_centroid_penn")
892892
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
@@ -1312,7 +1312,7 @@ def within(self, right: GeoSpatialValue) -> ir.BooleanValue:
13121312
>>> ibis.options.interactive = True
13131313
>>> import shapely
13141314
>>> t = ibis.examples.zones.fetch()
1315-
>>> penn_station_buff = shapely.Point(986345.399, 211974.446).buffer(5000)
1315+
>>> penn_station_buff = shapely.geometry.Point(986345.399, 211974.446).buffer(5000)
13161316
>>> penn_lit = ibis.literal(penn_station_buff, "geometry")
13171317
>>> t.filter(t.geom.within(penn_lit))["zone"]
13181318
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓

0 commit comments

Comments
 (0)