-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #2 from asfadmin/rew/pr-4721-add-ummg-helpers
PR-4721 Add transformations for UMM-G
- Loading branch information
Showing
12 changed files
with
438 additions
and
120 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,61 @@ | ||
# Geo Extensions | ||
This directory contains an installable python package with common functionality | ||
that may be used across different workflows. The intent is for the API to be | ||
designed in a generic and reusable way. Code that is very specific to its | ||
context should not be copy/pasted into this library! | ||
|
||
This library consists of some common functions needed to manipulate polygons | ||
before posting them to CMR. This includes functions to split polygons along | ||
the antimeridian and perform other 'clean up' operations. | ||
|
||
|
||
## Example Usage | ||
|
||
Polygons are manipulated using a composable pipeline of transformation | ||
functions. A transformation function is any function with the following | ||
signature: | ||
|
||
```python | ||
def transformation(polygon: Polygon) -> Generator[Polygon, None, None]: | ||
... | ||
``` | ||
|
||
A Transformer object is created with a list of transformations, and then can | ||
be reused to perform the same manipulation on many polygons. | ||
|
||
```python | ||
from geo_extensions import Transformer | ||
from geo_extensions.transformations import ( | ||
simplify_polygon, | ||
split_polygon_on_antimeridian, | ||
) | ||
|
||
|
||
def my_custom_transformation(polygon): | ||
"""Duplicate polygon""" | ||
|
||
yield polygon | ||
yield polygon | ||
|
||
|
||
transformer = Transformer([ | ||
simplify_polygon, | ||
my_custom_transformation, | ||
split_polygon_on_antimeridian, | ||
]) | ||
|
||
final_polygons = transformer.transform([ | ||
Polygon([ | ||
(150., 10.), (150., -10.), | ||
(-150., -10.), (-150., 10.), (150., 10.), | ||
]) | ||
]) | ||
``` | ||
|
||
The default transformer performs some standard transformations that are usually | ||
needed. Check the definition for what those transformations are. | ||
|
||
```python | ||
from geo_extensions import default_transformer | ||
|
||
|
||
WKT = "MULTIPOLYGON (((30 20, 45 40, 10 40, 30 20)), ((15 5, 40 10, 10 20, 5 10, 15 5)))" | ||
|
||
polygons = default_transformer.from_wkt(WKT) | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
from geo_extensions.checks import ( | ||
fixed_size_polygon_crosses_antimeridian, | ||
polygon_crosses_antimeridian, | ||
) | ||
from geo_extensions.transformations import ( | ||
reverse_polygon, | ||
simplify_polygon, | ||
split_polygon_on_antimeridian, | ||
) | ||
from geo_extensions.transformer import Transformer, to_polygons | ||
from geo_extensions.types import Transformation, TransformationResult | ||
|
||
default_transformer = Transformer([ | ||
simplify_polygon(0.1), | ||
split_polygon_on_antimeridian, | ||
]) | ||
|
||
|
||
__all__ = ( | ||
"default_transformer", | ||
"fixed_size_polygon_crosses_antimeridian", | ||
"polygon_crosses_antimeridian", | ||
"reverse_polygon", | ||
"simplify_polygon", | ||
"split_polygon_on_antimeridian", | ||
"to_polygons", | ||
"Transformation", | ||
"TransformationResult", | ||
"Transformer", | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
from shapely.geometry import Polygon | ||
|
||
|
||
def polygon_crosses_antimeridian(polygon: Polygon) -> bool: | ||
"""Checks if the longitude coordinates 'wrap around' the 180/-180 line. | ||
The polygon must be oriented in counter-clockwise order. | ||
:param polygon: the polygon to check | ||
""" | ||
|
||
# Polygons crossing the antimeridian will appear to be mis-ordered | ||
return not polygon.exterior.is_ccw | ||
|
||
|
||
def fixed_size_polygon_crosses_antimeridian( | ||
polygon: Polygon, | ||
min_lon_extent: float, | ||
) -> bool: | ||
"""Checks if the longitude coordinates 'wrap around' the 180/-180 line | ||
based on a heuristic that assumes the polygon is of a certain size. | ||
:param polygon: the polygon check | ||
:param min_lon_extent: the lower bound for the distance between the | ||
longitude values of the bounding box enclosing the entire polygon. | ||
Must be between (0, 180) exclusive. | ||
""" | ||
assert 0 < min_lon_extent < 180 | ||
|
||
min_lon, _, max_lon, _ = polygon.bounds | ||
dist_from_180 = 180 - min_lon_extent | ||
|
||
return max_lon > dist_from_180 or min_lon < -dist_from_180 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
from typing import Iterable, List, Sequence, Tuple | ||
|
||
from shapely import Geometry, wkt | ||
from shapely.geometry import MultiPolygon, Polygon | ||
|
||
from geo_extensions.types import Transformation, TransformationResult | ||
|
||
|
||
class Transformer: | ||
"""Apply a sequence of transformations to a polygon list.""" | ||
|
||
def __init__(self, transformations: Sequence[Transformation]): | ||
self.transformations = transformations | ||
|
||
def from_wkt(self, wkt_str: str) -> List[Polygon]: | ||
"""Load and transform an object from a WKT string. | ||
:returns: a list of transformed polygons | ||
:raises: ShapelyError, Exception | ||
""" | ||
|
||
obj = wkt.loads(wkt_str) | ||
polygons = to_polygons(obj) | ||
|
||
return self.transform(polygons) | ||
|
||
def transform(self, polygons: Iterable[Polygon]) -> List[Polygon]: | ||
"""Perform the transformation chain on a sequence of polygons. | ||
:returns: a list of transformed polygons | ||
""" | ||
|
||
return list( | ||
_apply_transformations( | ||
polygons, | ||
tuple(self.transformations), | ||
) | ||
) | ||
|
||
|
||
def to_polygons(obj: Geometry) -> TransformationResult: | ||
"""Convert a geometry to a sequence of polygons. | ||
:returns: a generator yielding the polygon sequence. | ||
:raises: Exception | ||
""" | ||
if isinstance(obj, MultiPolygon): | ||
for poly in obj.geoms: | ||
yield poly | ||
return | ||
|
||
if isinstance(obj, Polygon): | ||
yield obj | ||
return | ||
|
||
raise Exception(f"WKT: '{obj}' is not a Polygon or MultiPolygon") | ||
|
||
|
||
def _apply_transformations( | ||
polygons: Iterable[Polygon], | ||
transformations: Tuple[Transformation, ...], | ||
) -> TransformationResult: | ||
if not transformations: | ||
yield from polygons | ||
return | ||
|
||
transformation, transformations = transformations[0], transformations[1:] | ||
for polygon in polygons: | ||
yield from _apply_transformations( | ||
transformation(polygon), | ||
transformations, | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
from typing import Callable, Generator | ||
|
||
from shapely.geometry import Polygon | ||
|
||
TransformationResult = Generator[Polygon, None, None] | ||
Transformation = Callable[[Polygon], TransformationResult] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
from pathlib import Path | ||
|
||
import pytest | ||
from shapely.geometry import Polygon | ||
|
||
|
||
@pytest.fixture(scope="session") | ||
def data_path(): | ||
return Path(__file__).parent / "data" | ||
|
||
|
||
@pytest.fixture | ||
def rectangle(): | ||
"""A rectanglular polygon""" | ||
polygon = Polygon([ | ||
(160., 60.), (170., 60.), | ||
(170., 70.), (160., 70.), (160., 60.), | ||
]) | ||
assert polygon.exterior.is_ccw | ||
|
||
return polygon | ||
|
||
|
||
@pytest.fixture | ||
def centered_rectangle(): | ||
"""A rectanglular polygon centered at 0, 0""" | ||
polygon = Polygon([ | ||
(-30., 10.), (-30., -10.), | ||
(30., 10.), (30., -10.), (-30., 10.), | ||
]) | ||
assert polygon.exterior.is_ccw | ||
|
||
return polygon | ||
|
||
|
||
@pytest.fixture | ||
def antimeridian_centered_rectangle(): | ||
"""A rectanglular polygon centered over the antimeridian""" | ||
polygon = Polygon([ | ||
(150., 10.), (150., -10.), | ||
(-150., -10.), (-150., 10.), (150., 10.), | ||
]) | ||
assert not polygon.exterior.is_ccw | ||
|
||
return polygon |
1 change: 1 addition & 0 deletions
1
tests/data/OPERA_L2_RTC-S1_T114-243299-IW1_20230722T122534Z_20230818T184635Z_S1A_30_v0.4.wkt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
POLYGON ((-64.18242781701073 80.92318071697005, -64.39970970298715 80.9454570771648, -64.61612029680748 80.96740982711674, -64.82622920447335 80.98850229027714, -65.00748150944156 81.00652638894496, -65.22501282807744 81.02794426023328, -65.44169904250701 81.04905266333425, -65.65757823927397 81.06986003091683, -65.87268623449013 81.09037439986633, -66.08705749062148 81.11060324832292, -66.30072405980573 81.13055382372104, -66.54829004319721 81.1534050786742, -66.74847878940515 81.17168274713765, -66.9682347986952 81.19153758767904, -67.1873024177517 81.21111583503362, -67.40424480895076 81.23029529806435, -67.6140176947673 81.24864531978461, -67.77904468607704 81.26295037283721, -67.98805577215917 81.28089510750608, -68.19658874371407 81.29861264412553, -68.40445755029818 81.31609026531473, -68.93161319601728 81.16999707795055, -68.75880659254568 81.15552921084696, -68.56143568631921 81.13885228644183, -68.34755266640347 81.12059367126959, -68.1330656904868 81.10208558733731, -67.9342625866557 81.08475101083606, -67.72625351642775 81.06642916394016, -67.52646242103165 81.04865047626856, -67.31399914334948 81.02955002151907, -67.10086164611913 81.01018509417794, -66.85147397179965 80.98726773213718, -66.64005009157752 80.96761326971924, -66.42790019583806 80.94768347454357, -66.21499149127725 80.92747133311453, -66.00129003603924 80.90696928004046, -65.78675973774801 80.88616939816542, -65.5713624245361 80.86506333039286, -65.35505768449538 80.84364224556352, -65.17458029235257 80.82559142573987, -64.96085433090136 80.80401042325435, -64.74688665108201 80.78217738556877, -64.18242781701073 80.92318071697005)) |
Oops, something went wrong.