Skip to content

Commit

Permalink
simplify code
Browse files Browse the repository at this point in the history
  • Loading branch information
cleder committed Nov 5, 2023
1 parent 216525b commit 31e7733
Show file tree
Hide file tree
Showing 6 changed files with 100 additions and 146 deletions.
103 changes: 38 additions & 65 deletions pygeoif/factories.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
from typing import cast

from pygeoif.exceptions import WKTParserError
from pygeoif.functions import move_coordinates
from pygeoif.functions import move_geo_interface
from pygeoif.functions import signed_area
from pygeoif.geometry import Geometry
from pygeoif.geometry import GeometryCollection
Expand Down Expand Up @@ -61,70 +61,6 @@
mpre: Pattern[str] = re.compile(r"\(\((.+?)\)\)")


def force_2d(
context: Union[GeoType, GeoCollectionType],
) -> Union[Geometry, GeometryCollection]:
"""
Force the dimensionality of a geometry to 2D.
>>> force_2d(Point(0, 0, 1))
Point(0, 0)
>>> force_2d(Point(0, 0))
Point(0, 0)
>>> force_2d(LineString([(0, 0, 0), (0, 1, 1), (1, 1, 2)]))
LineString(((0, 0), (0, 1), (1, 1)))
"""
geometry = context if isinstance(context, dict) else mapping(context)
if not geometry:
msg = "Object does not implement __geo_interface__"
raise TypeError(msg)
if geometry["type"] == "GeometryCollection":
return GeometryCollection(
force_2d(g) # type: ignore [arg-type]
for g in geometry["geometries"] # type: ignore [typeddict-item]
)

geometry["coordinates"] = move_coordinates( # type: ignore [typeddict-unknown-key]
geometry["coordinates"], # type: ignore [typeddict-item]
(0, 0),
)
return shape(geometry)


def force_3d(
context: Union[GeoType, GeoCollectionType],
z: float = 0,
) -> Union[Geometry, GeometryCollection]:
"""
Force the dimensionality of a geometry to 3D.
>>> force_3d(Point(0, 0))
Point(0, 0, 0)
>>> force_3d(Point(0, 0), 1)
Point(0, 0, 1)
>>> force_3d(Point(0, 0, 0))
Point(0, 0, 0)
>>> force_3d(LineString([(0, 0), (0, 1), (1, 1)]))
LineString(((0, 0, 0), (0, 1, 0), (1, 1, 0)))
"""
geometry = context if isinstance(context, dict) else mapping(context)
if not geometry:
msg = "Object does not implement __geo_interface__"
raise TypeError(msg)
if geometry["type"] == "GeometryCollection":
return GeometryCollection(
force_3d(g, z) # type: ignore [arg-type]
for g in geometry["geometries"] # type: ignore [typeddict-item]
)

geometry["coordinates"] = move_coordinates( # type: ignore [typeddict-unknown-key]
geometry["coordinates"], # type: ignore [typeddict-item]
(0, 0, 0),
z,
)
return shape(geometry)


def get_oriented_ring(ring: LineType, ccw: bool) -> LineType: # noqa: FBT001
s = 1.0 if ccw else -1.0
return ring if signed_area(ring) / s >= 0 else ring[::-1]
Expand Down Expand Up @@ -396,6 +332,43 @@ def mapping(
return ob.__geo_interface__


def force_2d(
context: Union[GeoType, GeoCollectionType],
) -> Union[Geometry, GeometryCollection]:
"""
Force the dimensionality of a geometry to 2D.
>>> force_2d(Point(0, 0, 1))
Point(0, 0)
>>> force_2d(Point(0, 0))
Point(0, 0)
>>> force_2d(LineString([(0, 0, 0), (0, 1, 1), (1, 1, 2)]))
LineString(((0, 0), (0, 1), (1, 1)))
"""
geometry = mapping(context)
return shape(move_geo_interface(geometry, (0, 0)))


def force_3d(
context: Union[GeoType, GeoCollectionType],
z: float = 0,
) -> Union[Geometry, GeometryCollection]:
"""
Force the dimensionality of a geometry to 3D.
>>> force_3d(Point(0, 0))
Point(0, 0, 0)
>>> force_3d(Point(0, 0), 1)
Point(0, 0, 1)
>>> force_3d(Point(0, 0, 0))
Point(0, 0, 0)
>>> force_3d(LineString([(0, 0), (0, 1), (1, 1)]))
LineString(((0, 0, 0), (0, 1, 0), (1, 1, 0)))
"""
geometry = mapping(context)
return shape(move_geo_interface(geometry, (0, 0, z)))


__all__ = [
"force_2d",
"force_3d",
Expand Down
8 changes: 4 additions & 4 deletions pygeoif/feature.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,9 @@ def feature_geo_interface_equals(
my_interface["geometry"]["type"] == other_interface["geometry"].get("type"),
compare_coordinates(
coords=my_interface["geometry"]["coordinates"],
other=other_interface["geometry"].get(
other=other_interface["geometry"].get( # type: ignore [arg-type]
"coordinates",
), # type: ignore[arg-type]
),
),
],
)
Expand Down Expand Up @@ -128,7 +128,7 @@ def properties(self) -> Dict[str, Any]:
def __geo_interface__(self) -> GeoFeatureInterface:
"""Return the GeoInterface of the geometry with properties."""
geo_interface: GeoFeatureInterface = {
"type": self.__class__.__name__,
"type": "Feature",
"bbox": cast(Bounds, self._geometry.bounds),
"geometry": self._geometry.__geo_interface__,
"properties": self._properties,
Expand Down Expand Up @@ -220,7 +220,7 @@ def bounds(self) -> Bounds:
def __geo_interface__(self) -> GeoFeatureCollectionInterface:
"""Return the GeoInterface of the feature."""
return {
"type": self.__class__.__name__,
"type": "FeatureCollection",
"bbox": self.bounds,
"features": tuple(feature.__geo_interface__ for feature in self._features),
}
Expand Down
71 changes: 40 additions & 31 deletions pygeoif/functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,8 @@
import math
from itertools import groupby
from itertools import zip_longest
from typing import Any
from typing import Iterable
from typing import List
from typing import Sequence
from typing import Tuple
from typing import Union
from typing import cast
Expand All @@ -33,6 +31,7 @@
from pygeoif.types import LineType
from pygeoif.types import MultiCoordinatesType
from pygeoif.types import Point2D
from pygeoif.types import PointType


def signed_area(coords: LineType) -> float:
Expand Down Expand Up @@ -191,10 +190,9 @@ def compare_geo_interface(


def move_coordinate(
coordinate: Sequence[float],
move_by: Sequence[float],
z: float = 0,
) -> Tuple[float, ...]:
coordinate: PointType,
move_by: PointType,
) -> PointType:
"""
Move the coordinate by the given vector.
Expand All @@ -206,16 +204,19 @@ def move_coordinate(
>>> move_coordinate((0, 0), (-1, 1, 0))
(-1, 1, 0)
"""
if len(coordinate) > len(move_by):
return tuple(c + m for c, m in zip(coordinate, move_by))
return tuple(c + m for c, m in zip_longest(coordinate, move_by, fillvalue=z))
if len(coordinate) < len(move_by):
return cast(
PointType,
tuple(c + m for c, m in zip_longest(coordinate, move_by, fillvalue=0)),
)

return cast(PointType, tuple(c + m for c, m in zip(coordinate, move_by)))


def move_coordinates(
coordinates: Sequence[Any],
move_by: Sequence[float],
z: float = 0,
) -> Sequence[Any]:
coordinates: CoordinatesType,
move_by: PointType,
) -> CoordinatesType:
"""
Move the coordinates recursively by the given vector.
Expand All @@ -228,25 +229,34 @@ def move_coordinates(
>>> move_coordinates(((0, 0), (-1, 1)), (-1, 1, 0))
((-1, 1, 0), (-2, 2, 0))
"""
if is_coordinate(coordinates):
# a single coordinate
return move_coordinate(coordinates, move_by, z)
# a list of coordinates
return tuple(move_coordinates(c, move_by, z) for c in coordinates)
if isinstance(coordinates[0], (int, float)):
return move_coordinate(cast(PointType, coordinates), move_by)
return cast(
CoordinatesType,
tuple(move_coordinates(cast(CoordinatesType, c), move_by) for c in coordinates),
)


def is_coordinate(val: Any) -> bool: # noqa: ANN401
"""
Check if given value is a coordinate i.e. vector of generic dimensionality.
>>> is_coordinate((1, 0))
True
>>> is_coordinate(1)
False
>>> is_coordinate([(1, 2), (3, 4)])
False
"""
return isinstance(val, tuple) and all(isinstance(x, (int, float)) for x in val)
def move_geo_interface(
interface: Union[GeoInterface, GeoCollectionInterface],
move_by: PointType,
) -> Union[GeoInterface, GeoCollectionInterface]:
"""Move the coordinates of the geo interface by the given vector."""
if interface["type"] == "GeometryCollection":
return {
"type": "GeometryCollection",
"geometries": tuple(
move_geo_interface(g, move_by)
for g in interface["geometries"] # type: ignore [typeddict-item]
),
}
return {
"type": interface["type"],
"coordinates": move_coordinates(
interface["coordinates"], # type: ignore [typeddict-item, arg-type]
move_by,
),
}


__all__ = [
Expand All @@ -257,6 +267,5 @@ def is_coordinate(val: Any) -> bool: # noqa: ANN401
"dedupe",
"move_coordinate",
"move_coordinates",
"is_coordinate",
"signed_area",
]
45 changes: 16 additions & 29 deletions pygeoif/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,9 @@
from typing import Union

from typing_extensions import Literal
from typing_extensions import NotRequired
from typing_extensions import Protocol
from typing_extensions import TypedDict
from typing_extensions import TypedDict # for Python <3.11 with (Not)Required

Point2D = Tuple[float, float]
Point3D = Tuple[float, float, float]
Expand All @@ -48,53 +49,39 @@
MultiCoordinatesType = Sequence[CoordinatesType]


class GeoInterfaceBase(TypedDict):
class GeoInterface(TypedDict):
"""Required keys for the GeoInterface."""

type: str
coordinates: Union[CoordinatesType, MultiCoordinatesType]


class GeoInterface(GeoInterfaceBase, total=False):
"""GeoInterface provides an optional bbox."""

bbox: Bounds
bbox: NotRequired[Bounds]


class GeoCollectionInterface(TypedDict):
"""Geometry Collection Interface."""

type: Literal["GeometryCollection"]
geometries: Sequence[Union[GeoInterface, "GeoCollectionInterface"]]
bbox: NotRequired[Bounds]


class GeoFeatureInterfaceBase(TypedDict):
"""Required keys for the GeoInterface for Features."""

type: str
geometry: GeoInterface


class GeoFeatureInterface(GeoFeatureInterfaceBase, total=False):
class GeoFeatureInterface(TypedDict):
"""The GeoFeatureInterface has optional keys."""

bbox: Bounds
properties: Dict[str, Any]
id: Union[str, int] # noqa: A003


class GeoFeatureCollectionInterfaceBase(TypedDict):
"""Required Keys for the GeoInterface of a FeatureCollection."""

type: str
features: Sequence[GeoFeatureInterface]
type: Literal["Feature"]
bbox: NotRequired[Bounds]
properties: NotRequired[Dict[str, Any]]
id: NotRequired[Union[str, int]]
geometry: GeoInterface


class GeoFeatureCollectionInterface(GeoFeatureCollectionInterfaceBase, total=False):
class GeoFeatureCollectionInterface(TypedDict):
"""Bbox and id are optional keys for the GeoFeatureCollectionInterface."""

bbox: Bounds
id: Union[str, int] # noqa: A003
type: Literal["FeatureCollection"]
features: Sequence[GeoFeatureInterface]
bbox: NotRequired[Bounds]
id: NotRequired[Union[str, int]]


class GeoType(Protocol):
Expand Down
3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ dev = [
"pygeoif[complexity]",
"pygeoif[linting]",
"pygeoif[tests]",
"pygeoif[typing]",
]
linting = [
"black",
Expand Down Expand Up @@ -210,7 +211,7 @@ select = [
"W",
"YTT",
]
target-version = "py37"
target-version = "py38"

[tool.ruff.isort]
force-single-line = true
Expand Down
16 changes: 0 additions & 16 deletions tests/test_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
from pygeoif.functions import compare_geo_interface
from pygeoif.functions import convex_hull
from pygeoif.functions import dedupe
from pygeoif.functions import is_coordinate
from pygeoif.functions import signed_area


Expand Down Expand Up @@ -453,18 +452,3 @@ def test_compare_neq_empty_geo_interface() -> None:
}

assert compare_geo_interface(geo_if, {}) is False


def test_is_coordinate() -> None:
assert is_coordinate((1, 2)) is True
assert is_coordinate((1,)) is True


def test_is_coordinate_not_composite_coordinates() -> None:
assert is_coordinate([(1, 2)]) is False
assert is_coordinate(((1, 2),)) is False
assert is_coordinate((((1, 2),),)) is False


def test_is_coordinate_not_primitive() -> None:
assert is_coordinate(1) is False

0 comments on commit 31e7733

Please sign in to comment.