diff --git a/geo_extensions/__init__.py b/geo_extensions/__init__.py index 9ef0669..84027ff 100644 --- a/geo_extensions/__init__.py +++ b/geo_extensions/__init__.py @@ -6,6 +6,7 @@ densify_polygon, drop_z_coordinate, reverse_polygon, + round_points, simplify_polygon, split_polygon_on_antimeridian_ccw, split_polygon_on_antimeridian_fixed_size, @@ -19,6 +20,7 @@ "polygon_crosses_antimeridian_ccw", "polygon_crosses_antimeridian_fixed_size", "reverse_polygon", + "round_points", "simplify_polygon", "split_polygon_on_antimeridian_ccw", "split_polygon_on_antimeridian_fixed_size", diff --git a/geo_extensions/transformations/__init__.py b/geo_extensions/transformations/__init__.py index a3fe138..4fcfe4d 100644 --- a/geo_extensions/transformations/__init__.py +++ b/geo_extensions/transformations/__init__.py @@ -39,13 +39,18 @@ split_polygon_on_antimeridian_ccw, split_polygon_on_antimeridian_fixed_size, ) -from geo_extensions.transformations.general import drop_z_coordinate, reverse_polygon +from geo_extensions.transformations.general import ( + drop_z_coordinate, + reverse_polygon, + round_points, +) from geo_extensions.transformations.geodetic import densify_polygon __all__ = ( "densify_polygon", "drop_z_coordinate", "reverse_polygon", + "round_points", "simplify_polygon", "split_polygon_on_antimeridian_ccw", "split_polygon_on_antimeridian_fixed_size", diff --git a/geo_extensions/transformations/general.py b/geo_extensions/transformations/general.py index be5ee7a..9197542 100644 --- a/geo_extensions/transformations/general.py +++ b/geo_extensions/transformations/general.py @@ -2,9 +2,11 @@ the polygons are using. """ +from typing import SupportsIndex + from shapely.geometry import Polygon -from geo_extensions.types import TransformationResult +from geo_extensions.types import Transformation, TransformationResult def reverse_polygon(polygon: Polygon) -> TransformationResult: @@ -22,3 +24,31 @@ def drop_z_coordinate(polygon: Polygon) -> TransformationResult: for interior in polygon.interiors ], ) + + +def round_points(ndigits: SupportsIndex) -> Transformation: + """Create a transformation that rounds polygon points to a given number of + digits. + + :returns: a callable transformation using the passed parameters + """ + + def round_points_(polygon: Polygon) -> TransformationResult: + """Round the polygon's points.""" + yield Polygon( + shell=(_round_coord(coord, ndigits) for coord in polygon.exterior.coords), + holes=[ + # ruff hint + (_round_coord(coord, ndigits) for coord in interior.coords) + for interior in polygon.interiors + ], + ) + + return round_points_ + + +def _round_coord( + coords: tuple[float, ...], + ndigits: SupportsIndex, +) -> tuple[float, ...]: + return tuple(round(x, ndigits) for x in coords) diff --git a/tests/test_transformations.py b/tests/test_transformations.py index 99f211c..bf73d3a 100644 --- a/tests/test_transformations.py +++ b/tests/test_transformations.py @@ -8,6 +8,7 @@ from geo_extensions.transformations import ( densify_polygon, drop_z_coordinate, + round_points, simplify_polygon, split_polygon_on_antimeridian_ccw, split_polygon_on_antimeridian_fixed_size, @@ -236,6 +237,45 @@ def test_drop_z_coordinate_holes(): ] +def test_round_polygon_points(): + polygon = Polygon( + shell=[ + (20.123456789, 0.123456789), + (20.123456789, 10.123456789), + (20.123456789, 10.123456789), + (20.123456789, 0.123456789), + (20.123456789, 0.123456789), + ], + holes=[ + [ + (18.123456789, 2.123456789), + (18.123456789, 8.123456789), + (18.123456789, 8.123456789), + (18.123456789, 2.123456789), + ], + ], + ) + assert list(round_points(3)(polygon)) == [ + Polygon( + shell=[ + (20.123, 0.123), + (20.123, 10.123), + (20.123, 10.123), + (20.123, 0.123), + (20.123, 0.123), + ], + holes=[ + [ + (18.123, 2.123), + (18.123, 8.123), + (18.123, 8.123), + (18.123, 2.123), + ], + ], + ), + ] + + @given(polygon=strategies.rectangles()) @settings(suppress_health_check=[HealthCheck.filter_too_much]) def test_split_polygon_on_antimeridian_ccw_returns_ccw(polygon):