Skip to content

Commit c92c729

Browse files
committed
feat: add support for Shapely
1 parent 9a9bc93 commit c92c729

File tree

3 files changed

+76
-18
lines changed

3 files changed

+76
-18
lines changed

python/polyshell/__init__.py

Lines changed: 62 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,24 @@
1+
from collections.abc import Sequence
12
from enum import Enum
23
from typing import Literal, overload
34

45
from polyshell._polyshell import *
56

6-
Polygon = list[tuple[float, float]]
7+
__all__ = [
8+
"ReductionMethod",
9+
"ReductionMode",
10+
"reduce_polygon",
11+
"reduce_polygon_eps",
12+
"reduce_polygon_len",
13+
"reduce_polygon_auto",
14+
]
15+
16+
17+
Polygon = Sequence[tuple[float, float]]
18+
19+
20+
class NullClass:
21+
pass
722

823

924
class ReductionMethod(str, Enum):
@@ -18,13 +33,45 @@ class ReductionMode(str, Enum):
1833
AUTO = "auto"
1934

2035

36+
# Feature gates
37+
try:
38+
from shapely import Polygon as ShapelyPolygon
39+
40+
Polygon = Polygon | ShapelyPolygon
41+
except ImportError:
42+
ShapelyPolygon = NullClass
43+
44+
try:
45+
from numpy.typing import NDArray
46+
from numpy import ndarray
47+
48+
Polygon = Polygon | NDArray[float]
49+
except ImportError:
50+
ndarray = NullClass
51+
52+
53+
def into_polygon(obj: any) -> Sequence[tuple[float, float]]:
54+
"""Cast a polygon object into a supported type."""
55+
match obj:
56+
case [*_] as seq:
57+
return seq
58+
case ndarray() as arr:
59+
return arr
60+
case ShapelyPolygon(exterior=exterior):
61+
return exterior.coords
62+
case _:
63+
raise TypeError(
64+
f"{type(obj)} cannot be interpreted as Polygon object {ShapelyPolygon}"
65+
)
66+
67+
2168
@overload
2269
def reduce_polygon(
2370
polygon: Polygon,
2471
mode: Literal[ReductionMode.EPSILON],
2572
epsilon: float,
2673
method: ReductionMethod,
27-
) -> Polygon:
74+
) -> list[list[float]]:
2875
pass
2976

3077

@@ -34,14 +81,14 @@ def reduce_polygon(
3481
mode: Literal[ReductionMode.LENGTH],
3582
length: int,
3683
method: ReductionMethod,
37-
) -> Polygon:
84+
) -> list[list[float]]:
3885
pass
3986

4087

4188
@overload
4289
def reduce_polygon(
4390
polygon: Polygon, mode: Literal[ReductionMode.AUTO], method: ReductionMethod
44-
) -> Polygon:
91+
) -> list[list[float]]:
4592
pass
4693

4794

@@ -50,7 +97,7 @@ def reduce_polygon(
5097
mode: ReductionMode,
5198
*args,
5299
**kwargs,
53-
) -> Polygon:
100+
) -> list[list[float]]:
54101
match mode:
55102
case ReductionMode.EPSILON:
56103
return reduce_polygon_eps(polygon, *args, **kwargs)
@@ -66,7 +113,8 @@ def reduce_polygon(
66113

67114
def reduce_polygon_eps(
68115
polygon: Polygon, epsilon: float, method: ReductionMethod
69-
) -> Polygon:
116+
) -> list[list[float]]:
117+
polygon = into_polygon(polygon)
70118
match method:
71119
case ReductionMethod.CHARSHAPE:
72120
return reduce_polygon_char(polygon, epsilon, len(polygon))
@@ -84,7 +132,8 @@ def reduce_polygon_len(
84132
polygon: Polygon,
85133
length: int,
86134
method: ReductionMethod,
87-
) -> Polygon:
135+
) -> list[list[float]]:
136+
polygon = into_polygon(polygon)
88137
match method:
89138
case ReductionMethod.CHARSHAPE:
90139
return reduce_polygon_char(polygon, 0.0, length) # maximum length
@@ -98,11 +147,16 @@ def reduce_polygon_len(
98147
)
99148

100149

101-
def reduce_polygon_auto(polygon: Polygon, method: ReductionMethod) -> Polygon:
150+
def reduce_polygon_auto(polygon: Polygon, method: ReductionMethod) -> list[list[float]]:
151+
polygon = into_polygon(polygon)
102152
match method:
103153
case ReductionMethod.CHARSHAPE:
104154
raise NotImplementedError
105155
case ReductionMethod.RDP:
106156
raise NotImplementedError
107157
case ReductionMethod.VW:
108158
raise NotImplementedError
159+
case _:
160+
raise ValueError(
161+
f"Unknown reduction method. Must be one of {[e.value for e in ReductionMethod]}"
162+
)

python/polyshell/_polyshell.pyi

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
1+
from collections.abc import Sequence
2+
3+
__all__ = ["reduce_polygon_char", "reduce_polygon_rdp", "reduce_polygon_rdp"]
4+
5+
SupportsIntoVec = Sequence[tuple[float, float]]
6+
17
def reduce_polygon_char(
2-
polygon: list[tuple[float, float]], eps: float, len: int
3-
) -> list[tuple[float, float]]:
8+
polygon: SupportsIntoVec, eps: float, len: int
9+
) -> list[list[float]]:
410
"""Reduce a polygon while retaining coverage."""
511

6-
def reduce_polygon_rdp(
7-
polygon: list[tuple[float, float]], eps: float
8-
) -> list[tuple[float, float]]:
12+
def reduce_polygon_rdp(polygon: SupportsIntoVec, eps: float) -> list[list[float]]:
913
"""Reduce a polygon while retaining coverage."""
1014

1115
def reduce_polygon_vw(
12-
polygon: list[tuple[float, float]], eps: float, len: int
13-
) -> list[tuple[float, float]]:
16+
polygon: SupportsIntoVec, eps: float, len: int
17+
) -> list[list[float]]:
1418
"""Reduce a polygon while retaining coverage."""

tests/polygon_cases.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -127,8 +127,8 @@ def case_array(self) -> NDArray[np.floating]:
127127
]
128128
)
129129

130-
def case_shapely_coord_sequence(self) -> CoordinateSequence:
131-
"""A polygon as a shapely CoordinateSequence."""
130+
def case_shapely(self) -> ShapelyPolygon:
131+
"""A shapely Polygon."""
132132
return ShapelyPolygon(
133133
[
134134
[0.0, 0.0],
@@ -138,7 +138,7 @@ def case_shapely_coord_sequence(self) -> CoordinateSequence:
138138
[1.0, 0.0],
139139
[0.0, 0.0],
140140
]
141-
).exterior.coords
141+
)
142142

143143
def case_sequence(self) -> Sequence[tuple[float, float]]:
144144
"""A polygon as a custom type."""

0 commit comments

Comments
 (0)