-
Notifications
You must be signed in to change notification settings - Fork 743
[GH-2504] Geopandas: Implement force_3d #2512
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
Changes from all commits
e9fe11a
c0ee218
9d62bad
819a3ee
52f268b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -949,8 +949,70 @@ def force_2d(self): | |||||||||||||
| """ | ||||||||||||||
| return _delegate_to_geometry_column("force_2d", self) | ||||||||||||||
|
|
||||||||||||||
| # def force_3d(self, z=0): | ||||||||||||||
| # raise NotImplementedError("This method is not implemented yet.") | ||||||||||||||
| def force_3d(self, z=0.0): | ||||||||||||||
| """Force the dimensionality of a geometry to 3D. | ||||||||||||||
|
|
||||||||||||||
| 2D geometries will get the provided Z coordinate; 3D geometries | ||||||||||||||
| are unchanged (unless their Z coordinate is ``np.nan``). | ||||||||||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||
|
|
||||||||||||||
| Note: Sedona's behavior may differ from Geopandas' for M and ZM geometries. | ||||||||||||||
| For M geometries, Sedona will replace the M coordinate and add the Z coordinate. | ||||||||||||||
| For ZM geometries, Sedona will drop the M coordinate and retain the Z coordinate. | ||||||||||||||
|
|
||||||||||||||
| Parameters | ||||||||||||||
| ---------- | ||||||||||||||
| z : float | array_like (default 0) | ||||||||||||||
| Z coordinate to be assigned | ||||||||||||||
|
|
||||||||||||||
| Returns | ||||||||||||||
| ------- | ||||||||||||||
| GeoSeries | ||||||||||||||
|
|
||||||||||||||
| Examples | ||||||||||||||
| -------- | ||||||||||||||
| >>> from shapely import Polygon, LineString, Point | ||||||||||||||
| >>> from sedona.spark.geopandas import GeoSeries | ||||||||||||||
| >>> s = GeoSeries( | ||||||||||||||
| ... [ | ||||||||||||||
| ... Point(1, 2), | ||||||||||||||
| ... Point(0.5, 2.5, 2), | ||||||||||||||
| ... LineString([(1, 1), (0, 1), (1, 0)]), | ||||||||||||||
| ... Polygon([(0, 0), (0, 10), (10, 10)]), | ||||||||||||||
| ... ], | ||||||||||||||
| ... ) | ||||||||||||||
| >>> s | ||||||||||||||
| 0 POINT (1 2) | ||||||||||||||
| 1 POINT Z (0.5 2.5 2) | ||||||||||||||
| 2 LINESTRING (1 1, 0 1, 1 0) | ||||||||||||||
| 3 POLYGON ((0 0, 0 10, 10 10, 0 0)) | ||||||||||||||
| dtype: geometry | ||||||||||||||
|
|
||||||||||||||
| >>> s.force_3d() | ||||||||||||||
| 0 POINT Z (1 2 0) | ||||||||||||||
| 1 POINT Z (0.5 2.5 2) | ||||||||||||||
| 2 LINESTRING Z (1 1 0, 0 1 0, 1 0 0) | ||||||||||||||
| 3 POLYGON Z ((0 0 0, 0 10 0, 10 10 0, 0 0 0)) | ||||||||||||||
| dtype: geometry | ||||||||||||||
|
|
||||||||||||||
| Z coordinate can be specified as scalar: | ||||||||||||||
|
|
||||||||||||||
| >>> s.force_3d(4) | ||||||||||||||
| 0 POINT Z (1 2 4) | ||||||||||||||
| 1 POINT Z (0.5 2.5 2) | ||||||||||||||
| 2 LINESTRING Z (1 1 4, 0 1 4, 1 0 4) | ||||||||||||||
| 3 POLYGON Z ((0 0 4, 0 10 4, 10 10 4, 0 0 4)) | ||||||||||||||
| dtype: geometry | ||||||||||||||
|
|
||||||||||||||
| Or as an array-like (one value per geometry): | ||||||||||||||
|
|
||||||||||||||
| >>> s.force_3d(range(4)) | ||||||||||||||
| 0 POINT Z (1 2 0) | ||||||||||||||
| 1 POINT Z (0.5 2.5 2) | ||||||||||||||
| 2 LINESTRING Z (1 1 2, 0 1 2, 1 0 2) | ||||||||||||||
| 3 POLYGON Z ((0 0 3, 0 10 3, 10 10 3, 0 0 3)) | ||||||||||||||
| dtype: geometry | ||||||||||||||
| """ | ||||||||||||||
| return _delegate_to_geometry_column("force_3d", self, z) | ||||||||||||||
|
|
||||||||||||||
| # def line_merge(self, directed=False): | ||||||||||||||
| # raise NotImplementedError("This method is not implemented yet.") | ||||||||||||||
|
|
||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1520,7 +1520,95 @@ def test_force_2d(self): | |
| self.check_sgpd_equals_gpd(df_result, expected) | ||
|
|
||
| def test_force_3d(self): | ||
| pass | ||
| # 1. 2D geometries promoted to 3D with default z=0.0 | ||
| s = sgpd.GeoSeries( | ||
| [ | ||
| Point(1, 2), | ||
| Point(0.5, 2.5, 2), | ||
| Point(1, 1, np.nan), | ||
| LineString([(1, 1), (0, 1), (1, 0)]), | ||
| Polygon([(0, 0), (0, 10), (10, 10)]), | ||
| GeometryCollection( | ||
| [ | ||
| Point(1, 1), | ||
| LineString([(1, 1), (0, 1), (1, 0)]), | ||
| ] | ||
| ), | ||
| ] | ||
| ) | ||
| # Promote 2D to 3D with z=0, keep 3D as is | ||
| expected = gpd.GeoSeries( | ||
| [ | ||
| Point(1, 2, 0), | ||
| Point(0.5, 2.5, 2), | ||
| Point(1, 1, 0), | ||
| LineString([(1, 1, 0), (0, 1, 0), (1, 0, 0)]), | ||
| Polygon([(0, 0, 0), (0, 10, 0), (10, 10, 0), (0, 0, 0)]), | ||
| GeometryCollection( | ||
| [ | ||
| Point(1, 1, 0), | ||
| LineString([(1, 1, 0), (0, 1, 0), (1, 0, 0)]), | ||
| ] | ||
| ), | ||
| ] | ||
| ) | ||
| result = s.force_3d() | ||
| self.check_sgpd_equals_gpd(result, expected) | ||
|
|
||
| # 2. 2D geometries promoted to 3D with scalar z | ||
| expected = gpd.GeoSeries( | ||
| [ | ||
| Point(1, 2, 4), | ||
| Point(0.5, 2.5, 2), | ||
| Point(1, 1, 4), | ||
| LineString([(1, 1, 4), (0, 1, 4), (1, 0, 4)]), | ||
| Polygon([(0, 0, 4), (0, 10, 4), (10, 10, 4), (0, 0, 4)]), | ||
| GeometryCollection( | ||
| [ | ||
| Point(1, 1, 4), | ||
| LineString([(1, 1, 4), (0, 1, 4), (1, 0, 4)]), | ||
| ] | ||
| ), | ||
| ] | ||
| ) | ||
| result = s.force_3d(4) | ||
| self.check_sgpd_equals_gpd(result, expected) | ||
|
|
||
| # 3. Array-like z: use ps.Series | ||
| z = [0, 2, 2, 3, 4, 5] | ||
| expected = gpd.GeoSeries( | ||
| [ | ||
| Point(1, 2, 0), | ||
| Point(0.5, 2.5, 2), | ||
| Point(1, 1, 2), | ||
| LineString([(1, 1, 3), (0, 1, 3), (1, 0, 3)]), | ||
| Polygon([(0, 0, 4), (0, 10, 4), (10, 10, 4), (0, 0, 4)]), | ||
| GeometryCollection( | ||
| [ | ||
| Point(1, 1, 5), | ||
| LineString([(1, 1, 5), (0, 1, 5), (1, 0, 5)]), | ||
| ] | ||
| ), | ||
| ] | ||
| ) | ||
| result = s.force_3d(z) | ||
| self.check_sgpd_equals_gpd(result, expected) | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you add these cases below?
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For those curious about the difference in behavior, Geopandas returns the following instead. It's strange and inconsistent imo... I would guess it's unintentional and just a result of adding M support later on. gpd.GeoSeries([shapely.wkt.loads("POINT M (1 2 3)")]).force_3d(7.5)
0 POINT M (1 2 3)
gpd.GeoSeries([shapely.wkt.loads("POINT ZM (1 2 3 4)")]).force_3d(7.5)
0 POINT Z (1 2 7.5) |
||
|
|
||
| # 4. Ensure M and ZM geometries are handled correctly | ||
| s = sgpd.GeoSeries( | ||
| [ | ||
| shapely.wkt.loads("POINT M (1 2 3)"), | ||
| shapely.wkt.loads("POINT ZM (1 2 3 4)"), | ||
| ] | ||
| ) | ||
| result = s.force_3d(7.5) | ||
| expected = gpd.GeoSeries( | ||
| [ | ||
| shapely.wkt.loads("POINT Z (1 2 7.5)"), | ||
| shapely.wkt.loads("POINT Z (1 2 3)"), | ||
| ] | ||
| ) | ||
| self.check_sgpd_equals_gpd(result, expected) | ||
|
|
||
| def test_line_merge(self): | ||
| pass | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you add a test for this in
test_geoseries.py? I see that this is from the original geopandas docstring, so I'm not entirely sure if we follow the same behavior naturally in Sedona. If we don't let me know, and we can figure out what to do next.We could test something like whether
Point(1, 1, np.nan)becomesPoint(1, 1, 3)when calling.force_3d(3)