Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 29 additions & 2 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,44 @@ name: Lint
on:
pull_request:

permissions:
contents: read

jobs:
flake8:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: 3.8
python-version: 3.9

- uses: TrueBrain/actions-flake8@v2
with:
flake8_version: 6.0.0
max_line_length: 120
plugins: flake8-isort==6.0.0 flake8-quotes==3.3.2
plugins: flake8-isort==6.1.2 flake8-quotes==3.4.0 flake8-commas==4.0.0

mypy:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: 3.9

- run: |
pip install poetry
poetry install --with=mypy

- run: |
poetry run mypy \
--follow-untyped-imports \
--disallow-any-unimported \
--disallow-untyped-defs \
--check-untyped-defs \
--strict-equality \
--warn-redundant-casts \
--warn-unused-ignores \
geo_extensions
5 changes: 4 additions & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,15 @@ name: Test
on:
pull_request:

permissions:
contents: read

jobs:
pytest:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ['3.8', '3.9', '3.10', '3.11']
python-version: ['3.9', '3.10', '3.11', '3.12']
fail-fast: false

steps:
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ functions. A transformation function is any function with the following
signature:

```python
def transformation(polygon: Polygon) -> Generator[Polygon, None, None]:
def transformation(polygon: Polygon) -> Generator[Polygon]:
...
```

Expand Down
22 changes: 12 additions & 10 deletions geo_extensions/transformations.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
ordered in the shapely flat space.
"""

from typing import List, Tuple
from typing import cast

import shapely.ops
from shapely.geometry import LineString, Polygon
Expand All @@ -50,8 +50,8 @@
)
from geo_extensions.types import Transformation, TransformationResult

Point = Tuple[float, float]
Bbox = List[Point]
Point = tuple[float, float]
Bbox = list[Point]

ANTIMERIDIAN = LineString([(180, 90), (180, -90)])

Expand All @@ -63,7 +63,9 @@ def simplify_polygon(tolerance: float, preserve_topology: bool = True) -> Transf
"""
def simplify(polygon: Polygon) -> TransformationResult:
"""Perform a shapely simplify operation on the polygon."""
yield polygon.simplify(tolerance, preserve_topology)
# NOTE(reweeden): I have been unable to produce a situation where a
# polygon is simplified to a geometry other than Polygon.
yield cast(Polygon, polygon.simplify(tolerance, preserve_topology))

return simplify

Expand Down Expand Up @@ -139,7 +141,7 @@ def _shift_polygon(polygon: Polygon) -> Polygon:

return Polygon([
((360.0 + lon) % 360, lat)
for lon, lat in polygon.boundary.coords
for lon, lat in polygon.exterior.coords
])


Expand All @@ -149,7 +151,7 @@ def _shift_polygon_back(polygon: Polygon) -> Polygon:
_, _, max_lon, _ = polygon.bounds
return Polygon([
(_adjust_lon(lon, max_lon), lat)
for lon, lat in polygon.boundary.coords
for lon, lat in polygon.exterior.coords
])


Expand All @@ -165,13 +167,13 @@ def _adjust_lon(lon: float, max_lon: float) -> float:
def _split_polygon(
polygon: Polygon,
line: LineString,
) -> List[Polygon]:
) -> list[Polygon]:
split_collection = shapely.ops.split(polygon, line)

return [
orient(poly)
for poly in split_collection.geoms
if not _ignore_polygon(poly)
orient(geom)
for geom in split_collection.geoms
if isinstance(geom, Polygon) and not _ignore_polygon(geom)
]


Expand Down
12 changes: 6 additions & 6 deletions geo_extensions/transformer.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import Iterable, List, Sequence, Tuple
from collections.abc import Iterable, Sequence

from shapely import Geometry, wkt
from shapely.geometry import MultiPolygon, Polygon, shape
Expand All @@ -12,7 +12,7 @@ class Transformer:
def __init__(self, transformations: Sequence[Transformation]):
self.transformations = transformations

def from_geo_json(self, geo_json: dict) -> List[Polygon]:
def from_geo_json(self, geo_json: dict) -> list[Polygon]:
"""Load and transform an object from a GeoJSON dict.

:returns: a list of transformed polygons
Expand All @@ -24,7 +24,7 @@ def from_geo_json(self, geo_json: dict) -> List[Polygon]:

return self.transform(polygons)

def from_wkt(self, wkt_str: str) -> List[Polygon]:
def from_wkt(self, wkt_str: str) -> list[Polygon]:
"""Load and transform an object from a WKT string.

:returns: a list of transformed polygons
Expand All @@ -36,7 +36,7 @@ def from_wkt(self, wkt_str: str) -> List[Polygon]:

return self.transform(polygons)

def transform(self, polygons: Iterable[Polygon]) -> List[Polygon]:
def transform(self, polygons: Iterable[Polygon]) -> list[Polygon]:
"""Perform the transformation chain on a sequence of polygons.

:returns: a list of transformed polygons
Expand All @@ -46,7 +46,7 @@ def transform(self, polygons: Iterable[Polygon]) -> List[Polygon]:
_apply_transformations(
polygons,
tuple(self.transformations),
)
),
)


Expand All @@ -70,7 +70,7 @@ def to_polygons(obj: Geometry) -> TransformationResult:

def _apply_transformations(
polygons: Iterable[Polygon],
transformations: Tuple[Transformation, ...],
transformations: tuple[Transformation, ...],
) -> TransformationResult:
if not transformations:
yield from polygons
Expand Down
4 changes: 2 additions & 2 deletions geo_extensions/types.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from typing import Callable, Generator
from collections.abc import Callable, Generator

from shapely.geometry import Polygon

TransformationResult = Generator[Polygon, None, None]
TransformationResult = Generator[Polygon]
Transformation = Callable[[Polygon], TransformationResult]
7 changes: 6 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,17 @@ readme = "README.md"
packages = [{include = "geo_extensions"}]

[tool.poetry.dependencies]
python = "^3.8"
python = "^3.9"

shapely = "^2.0.3"

[tool.poetry.group.dev.dependencies]
pytest = "^8.0.1"
hypothesis = "^6.98.8"

[tool.poetry.group.mypy.dependencies]
mypy = "^1.16.1"
types-shapely = "^2.0.3"

[tool.isort]
profile = "black"
2 changes: 1 addition & 1 deletion tests/test_geo_extensions.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,5 @@ def test_default_transformer(data_path):
(-68.93161319601728, 81.16999707795055),
(-64.74688665108201, 80.78217738556877),
(-64.18242781701073, 80.92318071697005),
])
]),
]
55 changes: 49 additions & 6 deletions tests/test_transformations.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,54 @@

from geo_extensions.transformations import (
drop_z_coordinate,
simplify_polygon,
split_polygon_on_antimeridian_ccw,
split_polygon_on_antimeridian_fixed_size,
)


def test_simplify():
polygon = Polygon([
(20, 0),
(20, 0),
(20, 10),
(0, 10),
(0, 0),
(20, 0),
])
assert list(simplify_polygon(0.01)(polygon)) == [
Polygon([
(20, 0),
(20, 10),
(0, 10),
(0, 0),
(20, 0),
]),
]


def test_simplify_line():
polygon = Polygon([
(20, 0),
(20, 10),
(20, 10),
(20, 0),
(20, 0),
])
assert list(simplify_polygon(0.01)(polygon)) == [
Polygon([
(20, 0),
(20, 10),
(20, 10),
(20, 0),
]),
]

assert list(simplify_polygon(0.01, preserve_topology=False)(polygon)) == [
Polygon([]),
]


def test_drop_z_coordinate():
polygon = Polygon([
(180, 1, 10),
Expand All @@ -26,7 +69,7 @@ def test_drop_z_coordinate():
(-179.999, 0),
(-179.999, 1),
(180, 1),
])
]),
]


Expand All @@ -53,7 +96,7 @@ def test_split_polygon_on_antimeridian_ccw_returns_ccw(polygon):
polygon=strategies.rectangles(
# Very small polygons near the antimeridian will be culled.
lons=st.floats(min_value=-179.990, max_value=180),
)
),
)
@settings(suppress_health_check=[HealthCheck.filter_too_much])
def test_split_polygon_on_antimeridian_ccw_returns_non_empty_list(polygon):
Expand All @@ -64,7 +107,7 @@ def test_split_polygon_on_antimeridian_ccw_returns_empty_list():
# There is a case where the input polygon is really small, and both split
# parts are culled.
polygon = Polygon([
(180, 1), (180, 0), (-179.999, 0), (-179.999, 1), (180, 1)
(180, 1), (180, 0), (-179.999, 0), (-179.999, 1), (180, 1),
])
assert list(split_polygon_on_antimeridian_ccw(polygon)) == []

Expand Down Expand Up @@ -137,7 +180,7 @@ def test_split_polygon_on_antimeridian_ccw_west():
"""Polygon is mostly west of the IDL"""
polygon = Polygon([
(170., 70.), (170., 60.), (-179., 60.),
(-179., 70.), (170., 70.)
(-179., 70.), (170., 70.),
])
assert not polygon.exterior.is_ccw
polygons = list(split_polygon_on_antimeridian_ccw(polygon))
Expand Down Expand Up @@ -168,7 +211,7 @@ def test_split_polygon_on_antimeridian_ccw_east():
"""Polygon is mostly east of the IDL"""
polygon = Polygon([
(179., 70.), (179., 60.), (-170., 60.),
(-170., 70.), (179., 70.)
(-170., 70.), (179., 70.),
])
assert not polygon.exterior.is_ccw
polygons = list(split_polygon_on_antimeridian_ccw(polygon))
Expand Down Expand Up @@ -200,7 +243,7 @@ def test_split_polygon_on_antimeridian_ccw_close_point():
polygon = Polygon([
(179.999999, 70.),
(179., 60.), (-170., 60.),
(-170., 70.), (179., 70.)
(-170., 70.), (179., 70.),
])
assert not polygon.exterior.is_ccw
polygons = list(split_polygon_on_antimeridian_ccw(polygon))
Expand Down
8 changes: 4 additions & 4 deletions tests/test_transformer.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ def test_from_wkt(simplify_transformer):
(51.0, 21.0),
(51.0, 20.0),
(50.0, 20.0),
])
]),
]
assert simplify_transformer.from_wkt(
"POLYGON(( 1 1, 2 1, 1 2, 1 1))",
Expand All @@ -34,7 +34,7 @@ def test_from_wkt(simplify_transformer):
(2, 1),
(1, 2),
(1, 1),
])
]),
]
# Duplicate point is removed
assert simplify_transformer.from_wkt(
Expand All @@ -46,14 +46,14 @@ def test_from_wkt(simplify_transformer):
(51.0, 21.0),
(51.0, 20.0),
(50.0, 20.0),
])
]),
]


def test_from_wkt_multipolygon(simplify_transformer):
assert simplify_transformer.from_wkt(
"MULTIPOLYGON (((30 20, 45 40, 10 40, 30 20)),"
"((15 5, 40 10, 10 20, 5 10, 15 5)))"
"((15 5, 40 10, 10 20, 5 10, 15 5)))",
) == [
Polygon([
(30.0, 20.0),
Expand Down