Skip to content

Commit 79b1512

Browse files
authored
[GH-2725] Implement GeoSeries: type, unary_union, delaunay_triangles, voronoi_polygons, disjoint, m (#2726)
1 parent 8a077bb commit 79b1512

5 files changed

Lines changed: 391 additions & 39 deletions

File tree

python/sedona/spark/geopandas/base.py

Lines changed: 156 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -165,9 +165,31 @@ def geom_type(self):
165165
return _delegate_to_geometry_column("geom_type", self)
166166

167167
@property
168-
@abstractmethod
169168
def type(self):
170-
raise NotImplementedError("This method is not implemented yet.")
169+
"""Return the geometry type of each geometry in the GeoSeries.
170+
171+
This is an alias for :attr:`geom_type`.
172+
173+
Returns
174+
-------
175+
pandas.Series (str)
176+
177+
Examples
178+
--------
179+
>>> from sedona.spark.geopandas import GeoSeries
180+
>>> from shapely.geometry import Point, Polygon
181+
>>> s = GeoSeries(
182+
... [
183+
... Polygon([(0, 0), (1, 1), (0, 1)]),
184+
... Point(0, 0),
185+
... ]
186+
... )
187+
>>> s.type
188+
0 Polygon
189+
1 Point
190+
dtype: object
191+
"""
192+
return self.geom_type
171193

172194
@property
173195
def length(self):
@@ -772,11 +794,81 @@ def convex_hull(self):
772794
"""
773795
return _delegate_to_geometry_column("convex_hull", self)
774796

775-
# def delaunay_triangles(self, tolerance=0.0, only_edges=False):
776-
# raise NotImplementedError("This method is not implemented yet.")
797+
def delaunay_triangles(self, tolerance=0.0, only_edges=False):
798+
"""Return Delaunay triangulation of the vertices of each geometry.
777799
778-
# def voronoi_polygons(self, tolerance=0.0, extend_to=None, only_edges=False):
779-
# raise NotImplementedError("This method is not implemented yet.")
800+
.. note::
801+
802+
Unlike geopandas, which collects all vertices across the
803+
entire GeoSeries and computes a single triangulation, Sedona
804+
computes the triangulation **per row**. Each input geometry
805+
produces one ``GeometryCollection`` containing its triangles.
806+
The output GeoSeries has the same length as the input.
807+
808+
Parameters
809+
----------
810+
tolerance : float, default 0.0
811+
Snapping tolerance for vertices to be considered equal.
812+
only_edges : bool, default False
813+
If True, return only the edges of the triangulation as a
814+
MultiLineString. If False, return triangles as a
815+
GeometryCollection of Polygons.
816+
817+
Returns
818+
-------
819+
GeoSeries
820+
821+
Examples
822+
--------
823+
>>> from sedona.spark.geopandas import GeoSeries
824+
>>> from shapely.geometry import MultiPoint
825+
>>> s = GeoSeries([MultiPoint([(0, 0), (1, 0), (0.5, 1)])])
826+
>>> s.delaunay_triangles()
827+
0 GEOMETRYCOLLECTION (POLYGON ((0 0, 0.5 1, 1 0...
828+
dtype: geometry
829+
"""
830+
return _delegate_to_geometry_column(
831+
"delaunay_triangles", self, tolerance, only_edges
832+
)
833+
834+
def voronoi_polygons(self, tolerance=0.0, extend_to=None, only_edges=False):
835+
"""Return Voronoi diagram of the vertices of each geometry.
836+
837+
.. note::
838+
839+
Unlike geopandas, which collects all vertices across the
840+
entire GeoSeries and computes a single Voronoi diagram, Sedona
841+
computes the diagram **per row**. Each input geometry produces
842+
one ``GeometryCollection`` containing its Voronoi polygons.
843+
The output GeoSeries has the same length as the input.
844+
845+
Parameters
846+
----------
847+
tolerance : float, default 0.0
848+
Snapping tolerance for vertices to be considered equal.
849+
extend_to : Geometry, default None
850+
Not supported. Passing a non-None value will raise
851+
``NotImplementedError``.
852+
only_edges : bool, default False
853+
Only ``only_edges=False`` is supported. Passing ``only_edges=True``
854+
will raise ``NotImplementedError``.
855+
856+
Returns
857+
-------
858+
GeoSeries
859+
860+
Examples
861+
--------
862+
>>> from sedona.spark.geopandas import GeoSeries
863+
>>> from shapely.geometry import MultiPoint
864+
>>> s = GeoSeries([MultiPoint([(0, 0), (1, 0), (0.5, 1)])])
865+
>>> s.voronoi_polygons()
866+
0 GEOMETRYCOLLECTION (POLYGON ((-0.25 -0.5, -0....
867+
dtype: geometry
868+
"""
869+
return _delegate_to_geometry_column(
870+
"voronoi_polygons", self, tolerance, extend_to, only_edges
871+
)
780872

781873
@property
782874
def envelope(self):
@@ -1337,9 +1429,34 @@ def line_merge(self, directed=False):
13371429
"""
13381430
return _delegate_to_geometry_column("line_merge", self, directed)
13391431

1340-
# @property
1341-
# def unary_union(self):
1342-
# raise NotImplementedError("This method is not implemented yet.")
1432+
@property
1433+
def unary_union(self):
1434+
"""Returns a geometry containing the union of all geometries in the
1435+
``GeoSeries``.
1436+
1437+
Deprecated: The ``unary_union`` attribute is deprecated. Use
1438+
:meth:`union_all` instead.
1439+
1440+
Returns
1441+
-------
1442+
Geometry
1443+
1444+
Examples
1445+
--------
1446+
>>> from sedona.spark.geopandas import GeoSeries
1447+
>>> from shapely.geometry import box
1448+
>>> s = GeoSeries([box(0, 0, 1, 1), box(0, 0, 2, 2)])
1449+
>>> s.unary_union.wkt # doctest: +SKIP
1450+
'POLYGON ((0 1, 0 2, 2 2, 2 0, 1 0, 0 0, 0 1))'
1451+
"""
1452+
import warnings
1453+
1454+
warnings.warn(
1455+
"The 'unary_union' attribute is deprecated, use the 'union_all()' method instead.",
1456+
FutureWarning,
1457+
stacklevel=2,
1458+
)
1459+
return _delegate_to_geometry_column("union_all", self)
13431460

13441461
def union_all(self, method="unary", grid_size=None) -> BaseGeometry:
13451462
"""Returns a geometry containing the union of all geometries in the
@@ -1502,6 +1619,36 @@ def crosses(self, other, align=None) -> ps.Series:
15021619
"""
15031620
return _delegate_to_geometry_column("crosses", self, other, align)
15041621

1622+
def disjoint(self, other, align=None):
1623+
"""Returns a ``Series`` of ``dtype('bool')`` with value ``True`` for
1624+
each aligned geometry that is disjoint from `other`.
1625+
1626+
An object is said to be disjoint from `other` if its
1627+
`boundary` and `interior` do not intersect at all with those of the
1628+
other.
1629+
1630+
The operation works on a 1-to-1 row-wise manner.
1631+
1632+
Parameters
1633+
----------
1634+
other : GeoSeries or geometric object
1635+
The GeoSeries (elementwise) or geometric object to test if is
1636+
disjoint.
1637+
align : bool | None (default None)
1638+
If True, automatically aligns GeoSeries based on their indices. None defaults to True.
1639+
If False, the order of elements is preserved.
1640+
1641+
Returns
1642+
-------
1643+
Series (bool)
1644+
1645+
See also
1646+
--------
1647+
GeoSeries.intersects
1648+
GeoSeries.crosses
1649+
"""
1650+
return _delegate_to_geometry_column("disjoint", self, other, align)
1651+
15051652
def intersects(self, other, align=None):
15061653
"""Returns a ``Series`` of ``dtype('bool')`` with value ``True`` for
15071654
each aligned geometry that intersects `other`.

python/sedona/spark/geopandas/geodataframe.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1237,10 +1237,7 @@ def to_feather(
12371237

12381238
@property
12391239
def type(self):
1240-
# Implementation of the abstract method
1241-
raise NotImplementedError(
1242-
_not_implemented_error("type", "Returns numeric geometry type codes.")
1243-
)
1240+
return self.geom_type
12441241

12451242
def plot(self, *args, **kwargs):
12461243
"""

python/sedona/spark/geopandas/geoseries.py

Lines changed: 72 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -730,10 +730,7 @@ def geom_type(self) -> pspd.Series:
730730

731731
@property
732732
def type(self):
733-
# Implementation of the abstract method.
734-
raise NotImplementedError(
735-
_not_implemented_error("type", "Returns numeric geometry type codes.")
736-
)
733+
return self.geom_type
737734

738735
@property
739736
def length(self) -> pspd.Series:
@@ -984,12 +981,28 @@ def convex_hull(self) -> "GeoSeries":
984981
)
985982

986983
def delaunay_triangles(self, tolerance=0.0, only_edges=False):
987-
# Implementation of the abstract method.
988-
raise NotImplementedError("This method is not implemented yet.")
984+
spark_expr = stf.ST_DelaunayTriangles(
985+
self.spark.column, tolerance, int(only_edges)
986+
)
987+
return self._query_geometry_column(
988+
spark_expr,
989+
returns_geom=True,
990+
)
989991

990992
def voronoi_polygons(self, tolerance=0.0, extend_to=None, only_edges=False):
991-
# Implementation of the abstract method.
992-
raise NotImplementedError("This method is not implemented yet.")
993+
if only_edges:
994+
raise NotImplementedError(
995+
"Sedona does not support only_edges=True for voronoi_polygons."
996+
)
997+
if extend_to is not None:
998+
raise NotImplementedError(
999+
"Sedona does not support extend_to for voronoi_polygons."
1000+
)
1001+
spark_expr = stf.ST_VoronoiPolygons(self.spark.column, tolerance, extend_to)
1002+
return self._query_geometry_column(
1003+
spark_expr,
1004+
returns_geom=True,
1005+
)
9931006

9941007
@property
9951008
def envelope(self) -> "GeoSeries":
@@ -1144,8 +1157,14 @@ def line_merge(self, directed=False):
11441157

11451158
@property
11461159
def unary_union(self):
1147-
# Implementation of the abstract method.
1148-
raise NotImplementedError("This method is not implemented yet.")
1160+
import warnings
1161+
1162+
warnings.warn(
1163+
"The 'unary_union' attribute is deprecated, use the 'union_all()' method instead.",
1164+
FutureWarning,
1165+
stacklevel=2,
1166+
)
1167+
return self.union_all()
11491168

11501169
def union_all(self, method="unary", grid_size=None) -> BaseGeometry:
11511170
if grid_size is not None:
@@ -1202,9 +1221,18 @@ def crosses(self, other, align=None) -> pspd.Series:
12021221

12031222
return _to_bool(result)
12041223

1205-
def disjoint(self, other, align=None):
1206-
# Implementation of the abstract method.
1207-
raise NotImplementedError("This method is not implemented yet.")
1224+
def disjoint(self, other, align=None) -> pspd.Series:
1225+
other_series, extended = self._make_series_of_val(other)
1226+
align = False if extended else align
1227+
1228+
spark_expr = stp.ST_Disjoint(F.col("L"), F.col("R"))
1229+
result = self._row_wise_operation(
1230+
spark_expr,
1231+
other_series,
1232+
align,
1233+
default_val=False,
1234+
)
1235+
return _to_bool(result)
12081236

12091237
def intersects(
12101238
self, other: Union["GeoSeries", BaseGeometry], align: Union[bool, None] = None
@@ -1671,7 +1699,37 @@ def z(self) -> pspd.Series:
16711699
# GeoSeries-only (not in GeoDataFrame)
16721700
@property
16731701
def m(self) -> pspd.Series:
1674-
raise NotImplementedError("GeoSeries.m() is not implemented yet.")
1702+
"""Return the m coordinate of point geometries in a GeoSeries
1703+
1704+
Returns
1705+
-------
1706+
pandas.Series
1707+
1708+
Examples
1709+
--------
1710+
1711+
>>> from sedona.spark.geopandas import GeoSeries
1712+
>>> from shapely.geometry import Point
1713+
>>> s = GeoSeries([Point(1, 1), Point(2, 2), Point(3, 3)])
1714+
>>> s.m
1715+
0 NaN
1716+
1 NaN
1717+
2 NaN
1718+
dtype: float64
1719+
1720+
See Also
1721+
--------
1722+
1723+
GeoSeries.x
1724+
GeoSeries.y
1725+
GeoSeries.z
1726+
1727+
"""
1728+
spark_col = stf.ST_M(self.spark.column)
1729+
return self._query_geometry_column(
1730+
spark_col,
1731+
returns_geom=False,
1732+
)
16751733

16761734
# ============================================================================
16771735
# CONSTRUCTION METHODS

0 commit comments

Comments
 (0)