Skip to content

Commit

Permalink
feat(geometry): add "MultiPolyline2D" and "MultiPointList2D" class
Browse files Browse the repository at this point in the history
  • Loading branch information
zhen.chen committed Jul 9, 2021
1 parent 8dc54b4 commit 2f8965b
Show file tree
Hide file tree
Showing 4 changed files with 189 additions and 14 deletions.
3 changes: 2 additions & 1 deletion tensorbay/geometry/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from .box import Box2D, Box3D
from .keypoint import Keypoint2D, Keypoints2D
from .polygon import Polygon2D
from .polyline import Polyline2D
from .polyline import MultiPolyline2D, Polyline2D
from .transform import Transform3D
from .vector import Vector, Vector2D, Vector3D

Expand All @@ -19,6 +19,7 @@
"Keypoints2D",
"Polygon2D",
"Polyline2D",
"MultiPolyline2D",
"Transform3D",
"Vector",
"Vector2D",
Expand Down
101 changes: 91 additions & 10 deletions tensorbay/geometry/polygon.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"""

from typing import Dict, Iterable, List, Optional, Type, TypeVar
from typing import Any, Dict, Iterable, List, Optional, Type, TypeVar

from ..utility import UserMutableSequence, common_loads
from .box import Box2D
Expand Down Expand Up @@ -46,12 +46,6 @@ def __init__(
for point in points:
self._data.append(self._ElementType(*point))

def __eq__(self, other: object) -> bool:
if not isinstance(other, self.__class__):
return False

return self._data.__eq__(other._data)

def _loads(self: _P, contents: List[Dict[str, float]]) -> None:
self._data = []
for point in contents:
Expand All @@ -63,7 +57,7 @@ def loads(cls: Type[_P], contents: List[Dict[str, float]]) -> _P:
Arguments:
contents: A list of dictionaries containing the coordinates of the vertexes
of the polygon::
of the point list::
[
{
Expand Down Expand Up @@ -113,6 +107,93 @@ def bounds(self) -> Box2D:
return Box2D(x_min, y_min, x_max, y_max)


_L = TypeVar("_L", bound=PointList2D[Any])


class MultiPointList2D(UserMutableSequence[_L]):
"""This class defines the concept of MultiPointList2D.
:class:`MultiPointList2D` contains multiple 2D point lists.
Arguments:
point_lists: A list of 2D point list.
"""

_P = TypeVar("_P", bound="MultiPointList2D[_L]")

_ElementType: Type[_L]

def __init__(self, point_lists: Optional[Iterable[Iterable[Iterable[float]]]] = None) -> None:
self._data = (
[self._ElementType(point_list) for point_list in point_lists] if point_lists else []
)

def _loads(self: _P, contents: List[List[Dict[str, float]]]) -> None:
self._data = [self._ElementType.loads(point_list) for point_list in contents]

def _dumps(self) -> List[List[Dict[str, float]]]:
return [point_list.dumps() for point_list in self._data]

@classmethod
def loads(cls: Type[_P], contents: List[List[Dict[str, float]]]) -> _P:
"""Loads a :class:`MultiPointList2D` from the given contents.
Arguments:
contents: A list of dictionary lists containing the coordinates of the vertexes
of the multiple point lists::
[
[
{
"x": ...
"y": ...
},
...
]
...
]
Returns:
The loaded :class:`MultiPointList2D` object.
"""
return common_loads(cls, contents)

def dumps(self) -> List[List[Dict[str, float]]]:
"""Dumps all the information of the :class:`MultiPointList2D`.
Returns:
All the information of the :class:`MultiPointList2D`
"""
return self._dumps()

def bounds(self) -> Box2D:
"""Calculate the bounds of multiple point lists.
Returns:
The bounds of multiple point lists.
"""
x_min = x_max = self._data[0][0].x
y_min = y_max = self._data[0][0].y

for points in self._data:
box = points.bounds()
if box.xmin < x_min:
x_min = box.xmin
elif box.xmax > x_max:
x_max = box.xmax

if box.ymin < y_min:
y_min = box.ymin
elif box.ymax > y_max:
y_max = box.ymax

return Box2D(x_min, y_min, x_max, y_max)


class Polygon2D(PointList2D[Vector2D]):
"""This class defines the concept of Polygon2D.
Expand All @@ -135,10 +216,10 @@ class Polygon2D(PointList2D[Vector2D]):

@classmethod
def loads(cls: Type[_P], contents: List[Dict[str, float]]) -> _P:
"""Load a :class:`Polygon2D` from a list of dictionaries.
"""Loads the information of :class:`Polygon2D`.
Arguments:
contents: A list of dictionaries containing the coordinates
contents: A list of dictionary lists containing the coordinates
of the vertexes of the polygon.
Returns:
Expand Down
73 changes: 71 additions & 2 deletions tensorbay/geometry/polyline.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,15 @@
and provides a series of methods to operate on polyline, such as
:meth:`Polyline2D.uniform_frechet_distance` and :meth:`Polyline2D.similarity`.
:class:`MultiPolyline2D` contains a list of polyline.
"""

from itertools import accumulate, count, islice, product
from sys import version_info
from typing import Any, Dict, Iterable, List, Sequence, Tuple, Type, TypeVar
from typing import Any, Dict, Iterable, List, Optional, Sequence, Tuple, Type, TypeVar

from ..utility import common_loads
from .polygon import PointList2D
from .polygon import MultiPointList2D, PointList2D
from .vector import Vector2D

if version_info >= (3, 8):
Expand Down Expand Up @@ -204,3 +205,71 @@ def loads(cls: Type[_P], contents: List[Dict[str, float]]) -> _P:
"""
return common_loads(cls, contents)


class MultiPolyline2D(MultiPointList2D[Polyline2D]):
"""This class defines the concept of MultiPolyline2D.
:class:`MultiPolyline2D` contains a list of polylines.
Arguments:
polylines: A list of polylines.
Examples:
>>> MultiPolyline2D([[[1, 2], [2, 3]], [[3, 4], [6, 8]]])
MultiPolyline2D [
Polyline2D [...]
Polyline2D [...]
...
]
"""

_P = TypeVar("_P", bound="MultiPolyline2D")

_ElementType = Polyline2D

def __init__(self, polylines: Optional[Iterable[Iterable[Iterable[float]]]] = None) -> None:
super().__init__(polylines)

@classmethod
def loads(cls: Type[_P], contents: List[List[Dict[str, float]]]) -> _P:
"""Load a :class:`MultiPolyline2D` from the given contents.
Arguments:
contents: A list of dict lists containing
the coordinates of the vertexes of the polyline list.
Returns:
The loaded :class:`MultiPolyline2D` object.
Examples:
>>> contents = [[{'x': 1, 'y': 1}, {'x': 1, 'y': 2}, {'x': 2, 'y': 2}],
[{'x': 2, 'y': 3}, {'x': 3, 'y': 5}]]
>>> multipolyline = MultiPolyline2D.loads(contents)
>>> multipolyline
MultiPolyline2D [
Polyline2D [...]
Polyline2D [...]
...
]
"""
return common_loads(cls, contents)

def dumps(self) -> List[List[Dict[str, float]]]:
"""Dumps a :class:`MultiPolyline2D` into a polyline list.
Returns:
All the information of the :class:`MultiPolyline2D`.
Examples:
>>> multipolyline = MultiPolyline2D([[[1, 1], [1, 2], [2, 2]], [[2, 3], [3, 5]]])
>>> multipolyline.dumps()
[
[{'x': 1, 'y': 1}, {'x': 1, 'y': 2}, {'x': 2, 'y': 2}],
[{'x': 2, 'y': 3}, {'x': 3, 'y': 5}
]
"""
return self._dumps()
26 changes: 25 additions & 1 deletion tensorbay/geometry/tests/test_polyline.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,17 @@

from math import isclose

from .. import Box2D, Polyline2D, Vector2D
from .. import Box2D, MultiPolyline2D, Polyline2D, Vector2D

_POLYLINE_SEQUENCE_1 = [[1, 1], [2, 2], [4, 4], [5, 5]]
_POLYLINE_SEQUENCE_2 = [[2, 1], [4, 3], [6, 5]]

_POLYLINE_1 = Polyline2D(_POLYLINE_SEQUENCE_1)
_POLYLINE_2 = Polyline2D(_POLYLINE_SEQUENCE_2)

_MULTI_POLYLINE_SEQUENCE = [_POLYLINE_SEQUENCE_1, _POLYLINE_SEQUENCE_2]
_MULTI_POLYLINE = MultiPolyline2D([_POLYLINE_SEQUENCE_1, _POLYLINE_SEQUENCE_2])

_POLYLINE_INFO_1 = (
{
"index": 0,
Expand Down Expand Up @@ -55,6 +58,7 @@

_POLYLINE_CONTENT_1 = [{"x": 1, "y": 1}, {"x": 2, "y": 2}, {"x": 4, "y": 4}, {"x": 5, "y": 5}]
_POLYLINE_CONTENT_2 = [{"x": 2, "y": 1}, {"x": 4, "y": 3}, {"x": 6, "y": 5}]
_MULTI_POLYLINE_CONTENT = [_POLYLINE_CONTENT_1, _POLYLINE_CONTENT_2]


class TestPolyline2D:
Expand Down Expand Up @@ -109,3 +113,23 @@ def test_dumps(self):
def test_bounds(self):
assert _POLYLINE_1.bounds() == Box2D(1, 1, 5, 5)
assert _POLYLINE_2.bounds() == Box2D(2, 1, 6, 5)


class TestMultiPolyline2D:
def test_init(self):
assert MultiPolyline2D() == MultiPolyline2D([])
assert MultiPolyline2D(_MULTI_POLYLINE_SEQUENCE) == MultiPolyline2D(
[
[Vector2D(1, 1), Vector2D(2, 2), Vector2D(4, 4), Vector2D(5, 5)],
[Vector2D(2, 1), Vector2D(4, 3), Vector2D(6, 5)],
]
)

def test_loads(self):
assert MultiPolyline2D.loads(_MULTI_POLYLINE_CONTENT) == _MULTI_POLYLINE

def test_dumps(self):
assert _MULTI_POLYLINE.dumps() == _MULTI_POLYLINE_CONTENT

def test_bounds(self):
assert _MULTI_POLYLINE.bounds() == Box2D(1, 1, 6, 5)

0 comments on commit 2f8965b

Please sign in to comment.