from typing import Optional
from ground.hints import (Maybe,
Scalar)
from reprit.base import generate_repr
from .angle import Angle
from .compound import (Compound,
Indexable,
Linear,
Location,
Relation,
Shaped)
from .geometry import Geometry
from .iterable import non_negative_min
from .multipoint import Multipoint
from .packing import (MIN_MIX_NON_EMPTY_COMPONENTS,
pack_mix)
from .point import Point
class Mix(Indexable[Scalar]):
__slots__ = '_components', '_discrete', '_linear', '_shaped'
[docs] def __init__(self,
discrete: Maybe[Multipoint[Scalar]],
linear: Maybe[Linear[Scalar]],
shaped: Maybe[Shaped[Scalar]]) -> None:
"""
Initializes mix.
Time complexity:
``O(1)``
Memory complexity:
``O(1)``
"""
self._components = self._discrete, self._linear, self._shaped = (
discrete, linear, shaped
)
__repr__ = generate_repr(__init__)
[docs] def __and__(self, other: Compound[Scalar]) -> Compound[Scalar]:
"""
Returns intersection of the mix with the other geometry.
Time complexity:
``O(elements_count * log elements_count)``
Memory complexity:
``O(elements_count)``
where
.. code-block:: python
elements_count = discrete_size + linear_size\
+ shaped_vertices_count
discrete_size = len(points)
linear_size = len(segments)
shaped_vertices_count = (sum(len(polygon.border.vertices)
+ sum(len(hole.vertices)
for hole in polygon.holes)
for polygon in polygons)
points = [] if self.discrete is EMPTY else self.discrete.points
segments = ([]
if self.linear is EMPTY
else ([self.linear]
if isinstance(self.linear, Segment)
else self.linear.segments))
polygons = ([]
if self.shaped is EMPTY
else (self.shaped.polygons
if isinstance(self.linear, Multipolygon)
else [self.shaped]))
>>> from gon.base import (Contour, Mix, Multipoint, Point, Polygon,
... Segment)
>>> mix = Mix(Multipoint([Point(3, 3)]),
... Segment(Point(6, 6), Point(6, 8)),
... Polygon(Contour([Point(0, 0), Point(6, 0), Point(6, 6),
... Point(0, 6)]),
... [Contour([Point(2, 2), Point(2, 4), Point(4, 4),
... Point(4, 2)])]))
>>> mix & mix == mix
True
"""
discrete_part = self.discrete & other
linear_part = self.linear & other
shaped_part = self.shaped & other
context = self._context
if isinstance(linear_part, Multipoint):
shaped_part |= linear_part
linear_part = context.empty
elif isinstance(linear_part, Mix):
shaped_part |= linear_part.discrete
linear_part = linear_part.linear
if isinstance(shaped_part, Multipoint):
linear_part |= shaped_part
if isinstance(linear_part, Mix):
discrete_part |= linear_part.discrete
linear_part = linear_part.linear
shaped_part = context.empty
elif isinstance(shaped_part, Linear):
linear_part |= shaped_part
shaped_part = context.empty
elif isinstance(shaped_part, Mix):
linear_part = (linear_part | shaped_part.linear
| shaped_part.discrete)
shaped_part = shaped_part.shaped
if isinstance(linear_part, Multipoint):
discrete_part |= linear_part
linear_part = context.empty
elif isinstance(linear_part, Mix):
discrete_part |= linear_part.discrete
linear_part = linear_part.linear
return pack_mix(discrete_part, linear_part, shaped_part, context.empty,
context.mix_cls)
__rand__ = __and__
[docs] def __contains__(self, point: Point[Scalar]) -> bool:
"""
Checks if the mix contains the point.
Time complexity:
``O(log elements_count)`` expected after indexing,
``O(elements_count)`` worst after indexing or without it
Memory complexity:
``O(1)``
where
.. code-block:: python
elements_count = discrete_size + linear_size\
+ shaped_vertices_count
discrete_size = len(points)
linear_size = len(segments)
shaped_vertices_count = (sum(len(polygon.border.vertices)
+ sum(len(hole.vertices)
for hole in polygon.holes)
for polygon in polygons)
points = [] if self.discrete is EMPTY else self.discrete.points
segments = ([]
if self.linear is EMPTY
else ([self.linear]
if isinstance(self.linear, Segment)
else self.linear.segments))
polygons = ([]
if self.shaped is EMPTY
else (self.shaped.polygons
if isinstance(self.linear, Multipolygon)
else [self.shaped]))
>>> from gon.base import (Contour, Mix, Multipoint, Point, Polygon,
... Segment)
>>> mix = Mix(Multipoint([Point(3, 3)]),
... Segment(Point(6, 6), Point(6, 8)),
... Polygon(Contour([Point(0, 0), Point(6, 0), Point(6, 6),
... Point(0, 6)]),
... [Contour([Point(2, 2), Point(2, 4), Point(4, 4),
... Point(4, 2)])]))
>>> Point(0, 0) in mix
True
>>> Point(1, 1) in mix
True
>>> Point(2, 2) in mix
True
>>> Point(3, 3) in mix
True
>>> Point(4, 3) in mix
True
>>> Point(5, 2) in mix
True
>>> Point(6, 1) in mix
True
>>> Point(7, 0) in mix
False
"""
return bool(self.locate(point))
[docs] def __eq__(self, other: 'Mix[Scalar]') -> bool:
"""
Checks if mixes are equal.
Time complexity:
``O(elements_count)``
Memory complexity:
``O(1)``
where
.. code-block:: python
elements_count = discrete_size + linear_size\
+ shaped_vertices_count
discrete_size = len(points)
linear_size = len(segments)
shaped_vertices_count = (sum(len(polygon.border.vertices)
+ sum(len(hole.vertices)
for hole in polygon.holes)
for polygon in polygons)
points = [] if self.discrete is EMPTY else self.discrete.points
segments = ([]
if self.linear is EMPTY
else ([self.linear]
if isinstance(self.linear, Segment)
else self.linear.segments))
polygons = ([]
if self.shaped is EMPTY
else (self.shaped.polygons
if isinstance(self.linear, Multipolygon)
else [self.shaped]))
>>> from gon.base import (Contour, Mix, Multipoint, Point, Polygon,
... Segment)
>>> mix = Mix(Multipoint([Point(3, 3)]),
... Segment(Point(6, 6), Point(6, 8)),
... Polygon(Contour([Point(0, 0), Point(6, 0), Point(6, 6),
... Point(0, 6)]),
... [Contour([Point(2, 2), Point(2, 4), Point(4, 4),
... Point(4, 2)])]))
>>> mix == mix
True
"""
return self is other or (self._components == other._components
if isinstance(other, Mix)
else NotImplemented)
[docs] def __ge__(self, other: Compound[Scalar]) -> bool:
"""
Checks if the mix is a superset of the other geometry.
Time complexity:
``O(elements_count * log elements_count)``
Memory complexity:
``O(1)``
where
.. code-block:: python
elements_count = discrete_size + linear_size\
+ shaped_vertices_count
discrete_size = len(points)
linear_size = len(segments)
shaped_vertices_count = (sum(len(polygon.border.vertices)
+ sum(len(hole.vertices)
for hole in polygon.holes)
for polygon in polygons)
points = [] if self.discrete is EMPTY else self.discrete.points
segments = ([]
if self.linear is EMPTY
else ([self.linear]
if isinstance(self.linear, Segment)
else self.linear.segments))
polygons = ([]
if self.shaped is EMPTY
else (self.shaped.polygons
if isinstance(self.linear, Multipolygon)
else [self.shaped]))
>>> from gon.base import (Contour, Mix, Multipoint, Point, Polygon,
... Segment)
>>> mix = Mix(Multipoint([Point(3, 3)]),
... Segment(Point(6, 6), Point(6, 8)),
... Polygon(Contour([Point(0, 0), Point(6, 0), Point(6, 6),
... Point(0, 6)]),
... [Contour([Point(2, 2), Point(2, 4), Point(4, 4),
... Point(4, 2)])]))
>>> mix >= mix
True
"""
return (other is self._context.empty
or self == other
or ((self.shaped is not self._context.empty
or not isinstance(other, Shaped)
and (not isinstance(other, Mix)
or other.shaped is self._context.empty))
and self.relate(other) in (Relation.EQUAL,
Relation.COMPONENT,
Relation.ENCLOSED,
Relation.WITHIN)
if isinstance(other, Compound)
else NotImplemented))
[docs] def __gt__(self, other: Compound[Scalar]) -> bool:
"""
Checks if the mix is a strict superset of the other geometry.
Time complexity:
``O(elements_count * log elements_count)``
Memory complexity:
``O(1)``
where
.. code-block:: python
elements_count = discrete_size + linear_size\
+ shaped_vertices_count
discrete_size = len(points)
linear_size = len(segments)
shaped_vertices_count = (sum(len(polygon.border.vertices)
+ sum(len(hole.vertices)
for hole in polygon.holes)
for polygon in polygons)
points = [] if self.discrete is EMPTY else self.discrete.points
segments = ([]
if self.linear is EMPTY
else ([self.linear]
if isinstance(self.linear, Segment)
else self.linear.segments))
polygons = ([]
if self.shaped is EMPTY
else (self.shaped.polygons
if isinstance(self.linear, Multipolygon)
else [self.shaped]))
>>> from gon.base import (Contour, Mix, Multipoint, Point, Polygon,
... Segment)
>>> mix = Mix(Multipoint([Point(3, 3)]),
... Segment(Point(6, 6), Point(6, 8)),
... Polygon(Contour([Point(0, 0), Point(6, 0), Point(6, 6),
... Point(0, 6)]),
... [Contour([Point(2, 2), Point(2, 4), Point(4, 4),
... Point(4, 2)])]))
>>> mix > mix
False
"""
return (other is self._context.empty
or self != other
and ((self.shaped is not self._context.empty
or not isinstance(other, Shaped)
and (not isinstance(other, Mix)
or other.shaped is self._context.empty))
and self.relate(other) in (Relation.COMPONENT,
Relation.ENCLOSED,
Relation.WITHIN)
if isinstance(other, Compound)
else NotImplemented))
[docs] def __hash__(self) -> int:
"""
Returns hash value of the mix.
Time complexity:
``O(components_size)``
Memory complexity:
``O(1)``
where
.. code-block:: python
elements_count = discrete_size + linear_size\
+ shaped_vertices_count
discrete_size = len(points)
linear_size = len(segments)
shaped_vertices_count = (sum(len(polygon.border.vertices)
+ sum(len(hole.vertices)
for hole in polygon.holes)
for polygon in polygons)
points = [] if self.discrete is EMPTY else self.discrete.points
segments = ([]
if self.linear is EMPTY
else ([self.linear]
if isinstance(self.linear, Segment)
else self.linear.segments))
polygons = ([]
if self.shaped is EMPTY
else (self.shaped.polygons
if isinstance(self.linear, Multipolygon)
else [self.shaped]))
>>> from gon.base import (Contour, Mix, Multipoint, Point, Polygon,
... Segment)
>>> mix = Mix(Multipoint([Point(3, 3)]),
... Segment(Point(6, 6), Point(6, 8)),
... Polygon(Contour([Point(0, 0), Point(6, 0), Point(6, 6),
... Point(0, 6)]),
... [Contour([Point(2, 2), Point(2, 4), Point(4, 4),
... Point(4, 2)])]))
>>> hash(mix) == hash(mix)
True
"""
return hash(self._components)
[docs] def __le__(self, other: Compound[Scalar]) -> bool:
"""
Checks if the mix is a subset of the other geometry.
Time complexity:
``O(elements_count * log elements_count)``
Memory complexity:
``O(1)``
where
.. code-block:: python
elements_count = discrete_size + linear_size\
+ shaped_vertices_count
discrete_size = len(points)
linear_size = len(segments)
shaped_vertices_count = (sum(len(polygon.border.vertices)
+ sum(len(hole.vertices)
for hole in polygon.holes)
for polygon in polygons)
points = [] if self.discrete is EMPTY else self.discrete.points
segments = ([]
if self.linear is EMPTY
else ([self.linear]
if isinstance(self.linear, Segment)
else self.linear.segments))
polygons = ([]
if self.shaped is EMPTY
else (self.shaped.polygons
if isinstance(self.linear, Multipolygon)
else [self.shaped]))
>>> from gon.base import (Contour, Mix, Multipoint, Point, Polygon,
... Segment)
>>> mix = Mix(Multipoint([Point(3, 3)]),
... Segment(Point(6, 6), Point(6, 8)),
... Polygon(Contour([Point(0, 0), Point(6, 0), Point(6, 6),
... Point(0, 6)]),
... [Contour([Point(2, 2), Point(2, 4), Point(4, 4),
... Point(4, 2)])]))
>>> mix <= mix
True
"""
return (self == other
or (not isinstance(other, Multipoint)
and (self.shaped is self._context.empty
or not isinstance(other, Linear)
and (not isinstance(other, Mix)
or other.shaped is not self._context.empty))
and self.relate(other) in (Relation.COVER,
Relation.ENCLOSES,
Relation.COMPOSITE,
Relation.EQUAL)
if isinstance(other, Compound)
else NotImplemented))
[docs] def __lt__(self, other: Compound[Scalar]) -> bool:
"""
Checks if the mix is a strict subset of the other geometry.
Time complexity:
``O(elements_count * log elements_count)``
Memory complexity:
``O(1)``
where
.. code-block:: python
elements_count = discrete_size + linear_size\
+ shaped_vertices_count
discrete_size = len(points)
linear_size = len(segments)
shaped_vertices_count = (sum(len(polygon.border.vertices)
+ sum(len(hole.vertices)
for hole in polygon.holes)
for polygon in polygons)
points = [] if self.discrete is EMPTY else self.discrete.points
segments = ([]
if self.linear is EMPTY
else ([self.linear]
if isinstance(self.linear, Segment)
else self.linear.segments))
polygons = ([]
if self.shaped is EMPTY
else (self.shaped.polygons
if isinstance(self.linear, Multipolygon)
else [self.shaped]))
>>> from gon.base import (Contour, Mix, Multipoint, Point, Polygon,
... Segment)
>>> mix = Mix(Multipoint([Point(3, 3)]),
... Segment(Point(6, 6), Point(6, 8)),
... Polygon(Contour([Point(0, 0), Point(6, 0), Point(6, 6),
... Point(0, 6)]),
... [Contour([Point(2, 2), Point(2, 4), Point(4, 4),
... Point(4, 2)])]))
>>> mix < mix
False
"""
return (self != other
and (not isinstance(other, Multipoint)
and (self.shaped is self._context.empty
or not isinstance(other, Linear)
and (not isinstance(other, Mix)
or other.shaped is not self._context.empty))
and self.relate(other) in (Relation.COVER,
Relation.ENCLOSES,
Relation.COMPOSITE)
if isinstance(other, Compound)
else NotImplemented))
[docs] def __or__(self, other: Compound[Scalar]) -> Compound[Scalar]:
"""
Returns union of the mix with the other geometry.
Time complexity:
``O(elements_count * log elements_count)``
Memory complexity:
``O(elements_count)``
where
.. code-block:: python
elements_count = discrete_size + linear_size\
+ shaped_vertices_count
discrete_size = len(points)
linear_size = len(segments)
shaped_vertices_count = (sum(len(polygon.border.vertices)
+ sum(len(hole.vertices)
for hole in polygon.holes)
for polygon in polygons)
points = [] if self.discrete is EMPTY else self.discrete.points
segments = ([]
if self.linear is EMPTY
else ([self.linear]
if isinstance(self.linear, Segment)
else self.linear.segments))
polygons = ([]
if self.shaped is EMPTY
else (self.shaped.polygons
if isinstance(self.linear, Multipolygon)
else [self.shaped]))
>>> from gon.base import (Contour, Mix, Multipoint, Point, Polygon,
... Segment)
>>> mix = Mix(Multipoint([Point(3, 3)]),
... Segment(Point(6, 6), Point(6, 8)),
... Polygon(Contour([Point(0, 0), Point(6, 0), Point(6, 6),
... Point(0, 6)]),
... [Contour([Point(2, 2), Point(2, 4), Point(4, 4),
... Point(4, 2)])]))
>>> mix | mix == mix
True
"""
context = self._context
if isinstance(other, Multipoint):
return context.mix_cls(self.discrete
| (other - self.shaped - self.linear),
self.linear, self.shaped)
elif isinstance(other, Linear):
discrete_part, linear_part = self.discrete, self.linear
shaped_part = self.shaped | other
if isinstance(shaped_part, Linear):
linear_part = linear_part | shaped_part | discrete_part
shaped_part = context.empty
elif isinstance(shaped_part, Mix):
linear_part = linear_part | shaped_part.linear | discrete_part
shaped_part = shaped_part.shaped
else:
# other is subset of the shaped component
return pack_mix(discrete_part, linear_part, shaped_part,
context.empty, context.mix_cls)
if isinstance(linear_part, Mix):
discrete_part, linear_part = (linear_part.discrete,
linear_part.linear)
else:
discrete_part = context.empty
return pack_mix(discrete_part, linear_part, shaped_part,
context.empty, context.mix_cls)
elif isinstance(other, (Shaped, Mix)):
return self.shaped | other | self.linear | self.discrete
else:
return NotImplemented
__ror__ = __or__
[docs] def __rsub__(self, other: Compound[Scalar]) -> Compound[Scalar]:
"""
Returns difference of the other geometry with the mix.
Time complexity:
``O(elements_count * log elements_count)``
Memory complexity:
``O(1)``
where
.. code-block:: python
elements_count = discrete_size + linear_size\
+ shaped_vertices_count
discrete_size = len(points)
linear_size = len(segments)
shaped_vertices_count = (sum(len(polygon.border.vertices)
+ sum(len(hole.vertices)
for hole in polygon.holes)
for polygon in polygons)
points = [] if self.discrete is EMPTY else self.discrete.points
segments = ([]
if self.linear is EMPTY
else ([self.linear]
if isinstance(self.linear, Segment)
else self.linear.segments))
polygons = ([]
if self.shaped is EMPTY
else (self.shaped.polygons
if isinstance(self.linear, Multipolygon)
else [self.shaped]))
"""
return ((other - self.discrete) & (other - self.linear)
& other - self.shaped)
[docs] def __sub__(self, other: Compound[Scalar]) -> Compound[Scalar]:
"""
Returns difference of the mix with the other geometry.
Time complexity:
``O(elements_count * log elements_count)``
Memory complexity:
``O(1)``
where
.. code-block:: python
elements_count = discrete_size + linear_size\
+ shaped_vertices_count
discrete_size = len(points)
linear_size = len(segments)
shaped_vertices_count = (sum(len(polygon.border.vertices)
+ sum(len(hole.vertices)
for hole in polygon.holes)
for polygon in polygons)
points = [] if self.discrete is EMPTY else self.discrete.points
segments = ([]
if self.linear is EMPTY
else ([self.linear]
if isinstance(self.linear, Segment)
else self.linear.segments))
polygons = ([]
if self.shaped is EMPTY
else (self.shaped.polygons
if isinstance(self.linear, Multipolygon)
else [self.shaped]))
>>> from gon.base import (EMPTY, Contour, Mix, Multipoint, Point,
... Polygon, Segment)
>>> mix = Mix(Multipoint([Point(3, 3)]),
... Segment(Point(6, 6), Point(6, 8)),
... Polygon(Contour([Point(0, 0), Point(6, 0), Point(6, 6),
... Point(0, 6)]),
... [Contour([Point(2, 2), Point(2, 4), Point(4, 4),
... Point(4, 2)])]))
>>> mix - mix is EMPTY
True
"""
return pack_mix(self.discrete - other, self.linear - other,
self.shaped - other, self._context.empty,
self._context.mix_cls)
[docs] def __xor__(self, other: Compound[Scalar]) -> Compound[Scalar]:
"""
Returns symmetric difference of the mix with the other geometry.
Time complexity:
``O(elements_count * log elements_count)``
Memory complexity:
``O(elements_count)``
where
.. code-block:: python
elements_count = discrete_size + linear_size\
+ shaped_vertices_count
discrete_size = len(points)
linear_size = len(segments)
shaped_vertices_count = (sum(len(polygon.border.vertices)
+ sum(len(hole.vertices)
for hole in polygon.holes)
for polygon in polygons)
points = [] if self.discrete is EMPTY else self.discrete.points
segments = ([]
if self.linear is EMPTY
else ([self.linear]
if isinstance(self.linear, Segment)
else self.linear.segments))
polygons = ([]
if self.shaped is EMPTY
else (self.shaped.polygons
if isinstance(self.linear, Multipolygon)
else [self.shaped]))
>>> from gon.base import (EMPTY, Contour, Mix, Multipoint, Point,
... Polygon, Segment)
>>> mix = Mix(Multipoint([Point(3, 3)]),
... Segment(Point(6, 6), Point(6, 8)),
... Polygon(Contour([Point(0, 0), Point(6, 0), Point(6, 6),
... Point(0, 6)]),
... [Contour([Point(2, 2), Point(2, 4), Point(4, 4),
... Point(4, 2)])]))
>>> mix ^ mix is EMPTY
True
"""
context = self._context
if isinstance(other, Multipoint):
rest_other = other - self.shaped - self.linear
return pack_mix(self.discrete ^ rest_other, self.linear,
self.shaped, context.empty, context.mix_cls)
elif isinstance(other, Linear):
discrete_part, linear_part = self.discrete, self.linear
shaped_part = self.shaped ^ other
if isinstance(shaped_part, Linear):
linear_part = linear_part ^ shaped_part ^ discrete_part
shaped_part = context.empty
elif isinstance(shaped_part, Mix):
linear_part = linear_part ^ shaped_part.linear ^ discrete_part
shaped_part = shaped_part.shaped
else:
# other is subset of the shaped component
return pack_mix(discrete_part, linear_part, shaped_part,
context.empty, context.mix_cls)
if isinstance(linear_part, Mix):
discrete_part, linear_part = (linear_part.discrete,
linear_part.linear)
else:
discrete_part = context.empty
return pack_mix(discrete_part, linear_part, shaped_part,
context.empty, context.mix_cls)
elif isinstance(other, (Shaped, Mix)):
return self.shaped ^ other ^ self.linear ^ self.discrete
else:
return NotImplemented
__rxor__ = __xor__
@property
def centroid(self) -> Point[Scalar]:
"""
Returns centroid of the mix.
Time complexity:
``O(elements_count)``
Memory complexity:
``O(1)``
where
.. code-block:: python
elements_count = discrete_size + linear_size\
+ shaped_vertices_count
discrete_size = len(points)
linear_size = len(segments)
shaped_vertices_count = (sum(len(polygon.border.vertices)
+ sum(len(hole.vertices)
for hole in polygon.holes)
for polygon in polygons)
points = [] if self.discrete is EMPTY else self.discrete.points
segments = ([]
if self.linear is EMPTY
else ([self.linear]
if isinstance(self.linear, Segment)
else self.linear.segments))
polygons = ([]
if self.shaped is EMPTY
else (self.shaped.polygons
if isinstance(self.linear, Multipolygon)
else [self.shaped]))
>>> from gon.base import (Contour, Mix, Multipoint, Point, Polygon,
... Segment)
>>> mix = Mix(Multipoint([Point(3, 3)]),
... Segment(Point(6, 6), Point(6, 8)),
... Polygon(Contour([Point(0, 0), Point(6, 0), Point(6, 6),
... Point(0, 6)]),
... [Contour([Point(2, 2), Point(2, 4), Point(4, 4),
... Point(4, 2)])]))
>>> mix.centroid == Point(3, 3)
True
"""
return (self.linear
if self.shaped is self._context.empty
else self.shaped).centroid
@property
def discrete(self) -> Maybe[Multipoint[Scalar]]:
"""
Returns disrete component of the mix.
Time complexity:
``O(1)``
Memory complexity:
``O(1)``
>>> from gon.base import (Contour, Mix, Multipoint, Point, Polygon,
... Segment)
>>> mix = Mix(Multipoint([Point(3, 3)]),
... Segment(Point(6, 6), Point(6, 8)),
... Polygon(Contour([Point(0, 0), Point(6, 0), Point(6, 6),
... Point(0, 6)]),
... [Contour([Point(2, 2), Point(2, 4), Point(4, 4),
... Point(4, 2)])]))
>>> mix.discrete == Multipoint([Point(3, 3)])
True
"""
return self._discrete
@property
def shaped(self) -> Maybe[Shaped[Scalar]]:
"""
Returns shaped component of the mix.
Time complexity:
``O(1)``
Memory complexity:
``O(1)``
>>> from gon.base import (Contour, Mix, Multipoint, Point, Polygon,
... Segment)
>>> mix = Mix(Multipoint([Point(3, 3)]),
... Segment(Point(6, 6), Point(6, 8)),
... Polygon(Contour([Point(0, 0), Point(6, 0), Point(6, 6),
... Point(0, 6)]),
... [Contour([Point(2, 2), Point(2, 4), Point(4, 4),
... Point(4, 2)])]))
>>> from gon.base import Contour
>>> mix.shaped == Polygon(Contour([Point(0, 0), Point(6, 0),
... Point(6, 6), Point(0, 6)]),
... [Contour([Point(2, 2), Point(2, 4),
... Point(4, 4), Point(4, 2)])])
True
"""
return self._shaped
@property
def linear(self) -> Maybe[Linear[Scalar]]:
"""
Returns linear component of the mix.
Time complexity:
``O(1)``
Memory complexity:
``O(1)``
>>> from gon.base import (Contour, Mix, Multipoint, Point, Polygon,
... Segment)
>>> mix = Mix(Multipoint([Point(3, 3)]),
... Segment(Point(6, 6), Point(6, 8)),
... Polygon(Contour([Point(0, 0), Point(6, 0), Point(6, 6),
... Point(0, 6)]),
... [Contour([Point(2, 2), Point(2, 4), Point(4, 4),
... Point(4, 2)])]))
>>> mix.linear == Segment(Point(6, 6), Point(6, 8))
True
"""
return self._linear
[docs] def distance_to(self, other: Geometry[Scalar]) -> Scalar:
"""
Returns distance between the mix and the other geometry.
Time complexity:
``O(elements_count)``
Memory complexity:
``O(1)``
where
.. code-block:: python
elements_count = discrete_size + linear_size\
+ shaped_vertices_count
discrete_size = len(points)
linear_size = len(segments)
shaped_vertices_count = (sum(len(polygon.border.vertices)
+ sum(len(hole.vertices)
for hole in polygon.holes)
for polygon in polygons)
points = [] if self.discrete is EMPTY else self.discrete.points
segments = ([]
if self.linear is EMPTY
else ([self.linear]
if isinstance(self.linear, Segment)
else self.linear.segments))
polygons = ([]
if self.shaped is EMPTY
else (self.shaped.polygons
if isinstance(self.linear, Multipolygon)
else [self.shaped]))
>>> from gon.base import (Contour, Mix, Multipoint, Point, Polygon,
... Segment)
>>> mix = Mix(Multipoint([Point(3, 3)]),
... Segment(Point(6, 6), Point(6, 8)),
... Polygon(Contour([Point(0, 0), Point(6, 0), Point(6, 6),
... Point(0, 6)]),
... [Contour([Point(2, 2), Point(2, 4), Point(4, 4),
... Point(4, 2)])]))
>>> mix.distance_to(mix) == 0
True
"""
return non_negative_min(component.distance_to(other)
for component in self._components
if component is not self._context.empty)
[docs] def index(self) -> None:
"""
Pre-processes the mix to potentially improve queries.
Time complexity:
``O(elements_count * log elements_count)`` expected,
``O(elements_count ** 2)`` worst
Memory complexity:
``O(elements_count)``
where
.. code-block:: python
elements_count = discrete_size + linear_size\
+ shaped_vertices_count
discrete_size = len(points)
linear_size = len(segments)
shaped_vertices_count = (sum(len(polygon.border.vertices)
+ sum(len(hole.vertices)
for hole in polygon.holes)
for polygon in polygons)
points = [] if self.discrete is EMPTY else self.discrete.points
segments = ([]
if self.linear is EMPTY
else ([self.linear]
if isinstance(self.linear, Segment)
else self.linear.segments))
polygons = ([]
if self.shaped is EMPTY
else (self.shaped.polygons
if isinstance(self.linear, Multipolygon)
else [self.shaped]))
>>> from gon.base import (Contour, Mix, Multipoint, Point, Polygon,
... Segment)
>>> mix = Mix(Multipoint([Point(3, 3)]),
... Segment(Point(6, 6), Point(6, 8)),
... Polygon(Contour([Point(0, 0), Point(6, 0), Point(6, 6),
... Point(0, 6)]),
... [Contour([Point(2, 2), Point(2, 4), Point(4, 4),
... Point(4, 2)])]))
>>> mix.index()
"""
if isinstance(self.discrete, Indexable):
self.discrete.index()
if isinstance(self.linear, Indexable):
self.linear.index()
if isinstance(self.shaped, Indexable):
self.shaped.index()
[docs] def locate(self, point: Point[Scalar]) -> Location:
"""
Finds location of the point relative to the mix.
Time complexity:
``O(log elements_count)`` expected after indexing,
``O(elements_count)`` worst after indexing or without it
Memory complexity:
``O(1)``
where
.. code-block:: python
elements_count = discrete_size + linear_size\
+ shaped_vertices_count
discrete_size = len(points)
linear_size = len(segments)
shaped_vertices_count = (sum(len(polygon.border.vertices)
+ sum(len(hole.vertices)
for hole in polygon.holes)
for polygon in polygons)
points = [] if self.discrete is EMPTY else self.discrete.points
segments = ([]
if self.linear is EMPTY
else ([self.linear]
if isinstance(self.linear, Segment)
else self.linear.segments))
polygons = ([]
if self.shaped is EMPTY
else (self.shaped.polygons
if isinstance(self.linear, Multipolygon)
else [self.shaped]))
>>> from gon.base import (Contour, Mix, Multipoint, Point, Polygon,
... Segment)
>>> mix = Mix(Multipoint([Point(3, 3)]),
... Segment(Point(6, 6), Point(6, 8)),
... Polygon(Contour([Point(0, 0), Point(6, 0), Point(6, 6),
... Point(0, 6)]),
... [Contour([Point(2, 2), Point(2, 4), Point(4, 4),
... Point(4, 2)])]))
>>> mix.locate(Point(0, 0)) is Location.BOUNDARY
True
>>> mix.locate(Point(1, 1)) is Location.INTERIOR
True
>>> mix.locate(Point(2, 2)) is Location.BOUNDARY
True
>>> mix.locate(Point(3, 3)) is Location.BOUNDARY
True
>>> mix.locate(Point(4, 3)) is Location.BOUNDARY
True
>>> mix.locate(Point(5, 2)) is Location.INTERIOR
True
>>> mix.locate(Point(6, 1)) is Location.BOUNDARY
True
>>> mix.locate(Point(7, 0)) is Location.EXTERIOR
True
"""
for candidate in self._components:
location = candidate.locate(point)
if location is not Location.EXTERIOR:
return location
return Location.EXTERIOR
[docs] def relate(self, other: Compound[Scalar]) -> Relation:
"""
Finds relation between the mix and the other geometry.
Time complexity:
``O(elements_count * log elements_count)``
Memory complexity:
``O(elements_count)``
where
.. code-block:: python
elements_count = discrete_size + linear_size\
+ shaped_vertices_count
discrete_size = len(points)
linear_size = len(segments)
shaped_vertices_count = (sum(len(polygon.border.vertices)
+ sum(len(hole.vertices)
for hole in polygon.holes)
for polygon in polygons)
points = [] if self.discrete is EMPTY else self.discrete.points
segments = ([]
if self.linear is EMPTY
else ([self.linear]
if isinstance(self.linear, Segment)
else self.linear.segments))
polygons = ([]
if self.shaped is EMPTY
else (self.shaped.polygons
if isinstance(self.linear, Multipolygon)
else [self.shaped]))
>>> from gon.base import (Contour, Mix, Multipoint, Point, Polygon,
... Segment)
>>> mix = Mix(Multipoint([Point(3, 3)]),
... Segment(Point(6, 6), Point(6, 8)),
... Polygon(Contour([Point(0, 0), Point(6, 0), Point(6, 6),
... Point(0, 6)]),
... [Contour([Point(2, 2), Point(2, 4), Point(4, 4),
... Point(4, 2)])]))
>>> mix.relate(mix) is Relation.EQUAL
True
"""
return (self._relate_discrete(other)
if isinstance(other, Multipoint)
else (self._relate_linear(other)
if isinstance(other, Linear)
else (self._relate_shaped(other)
if isinstance(other, Shaped)
else (self._relate_mix(other)
if isinstance(other, Mix)
else other.relate(self).complement))))
[docs] def rotate(self,
angle: Angle,
point: Optional[Point[Scalar]] = None) -> 'Mix[Scalar]':
"""
Rotates the mix by given angle around given point.
Time complexity:
``O(elements_count)``
Memory complexity:
``O(elements_count)``
where
.. code-block:: python
elements_count = discrete_size + linear_size\
+ shaped_vertices_count
discrete_size = len(points)
linear_size = len(segments)
shaped_vertices_count = (sum(len(polygon.border.vertices)
+ sum(len(hole.vertices)
for hole in polygon.holes)
for polygon in polygons)
points = [] if self.discrete is EMPTY else self.discrete.points
segments = ([]
if self.linear is EMPTY
else ([self.linear]
if isinstance(self.linear, Segment)
else self.linear.segments))
polygons = ([]
if self.shaped is EMPTY
else (self.shaped.polygons
if isinstance(self.linear, Multipolygon)
else [self.shaped]))
>>> from gon.base import (Angle, Contour, Mix, Multipoint, Point,
... Polygon, Segment)
>>> mix = Mix(Multipoint([Point(3, 3)]),
... Segment(Point(6, 6), Point(6, 8)),
... Polygon(Contour([Point(0, 0), Point(6, 0), Point(6, 6),
... Point(0, 6)]),
... [Contour([Point(2, 2), Point(2, 4), Point(4, 4),
... Point(4, 2)])]))
>>> mix.rotate(Angle(1, 0)) == mix
True
>>> (mix.rotate(Angle(0, 1), Point(1, 1))
... == Mix(Multipoint([Point(-1, 3)]),
... Segment(Point(-4, 6), Point(-6, 6)),
... Polygon(Contour([Point(2, 0), Point(2, 6), Point(-4, 6),
... Point(-4, 0)]),
... [Contour([Point(0, 2), Point(-2, 2), Point(-2, 4),
... Point(0, 4)])])))
True
"""
return self._context.mix_cls(self.discrete.rotate(angle, point),
self.linear.rotate(angle, point),
self.shaped.rotate(angle, point))
[docs] def scale(self,
factor_x: Scalar,
factor_y: Optional[Scalar] = None) -> Compound[Scalar]:
"""
Scales the mix by given factor.
Time complexity:
``O(elements_count)``
Memory complexity:
``O(elements_count)``
where
.. code-block:: python
elements_count = discrete_size + linear_size\
+ shaped_vertices_count
discrete_size = len(points)
linear_size = len(segments)
shaped_vertices_count = (sum(len(polygon.border.vertices)
+ sum(len(hole.vertices)
for hole in polygon.holes)
for polygon in polygons)
points = [] if self.discrete is EMPTY else self.discrete.points
segments = ([]
if self.linear is EMPTY
else ([self.linear]
if isinstance(self.linear, Segment)
else self.linear.segments))
polygons = ([]
if self.shaped is EMPTY
else (self.shaped.polygons
if isinstance(self.linear, Multipolygon)
else [self.shaped]))
>>> from gon.base import (Contour, Mix, Multipoint, Point, Polygon,
... Segment)
>>> mix = Mix(Multipoint([Point(3, 3)]),
... Segment(Point(6, 6), Point(6, 8)),
... Polygon(Contour([Point(0, 0), Point(6, 0), Point(6, 6),
... Point(0, 6)]),
... [Contour([Point(2, 2), Point(2, 4), Point(4, 4),
... Point(4, 2)])]))
>>> mix.scale(1) == mix
True
>>> (mix.scale(1, 2)
... == Mix(Multipoint([Point(3, 6)]),
... Segment(Point(6, 12), Point(6, 16)),
... Polygon(Contour([Point(0, 0), Point(6, 0), Point(6, 12),
... Point(0, 12)]),
... [Contour([Point(2, 4), Point(2, 8), Point(4, 8),
... Point(4, 4)])])))
True
"""
if factor_y is None:
factor_y = factor_x
return (self._context.mix_cls(self.discrete.scale(factor_x, factor_y),
self.linear.scale(factor_x, factor_y),
self.shaped.scale(factor_x, factor_y))
if factor_x and factor_y
else ((self.discrete.scale(factor_x, factor_y)
| self.linear.scale(factor_x, factor_y)
| self.shaped.scale(factor_x, factor_y))
if factor_x or factor_y
else
self._context.multipoint_cls(
[self._context.point_cls(factor_x, factor_y)]
)))
[docs] def translate(self, step_x: Scalar, step_y: Scalar) -> 'Mix[Scalar]':
"""
Translates the mix by given step.
Time complexity:
``O(elements_count)``
Memory complexity:
``O(elements_count)``
where
.. code-block:: python
elements_count = discrete_size + linear_size\
+ shaped_vertices_count
discrete_size = len(points)
linear_size = len(segments)
shaped_vertices_count = (sum(len(polygon.border.vertices)
+ sum(len(hole.vertices)
for hole in polygon.holes)
for polygon in polygons)
points = [] if self.discrete is EMPTY else self.discrete.points
segments = ([]
if self.linear is EMPTY
else ([self.linear]
if isinstance(self.linear, Segment)
else self.linear.segments))
polygons = ([]
if self.shaped is EMPTY
else (self.shaped.polygons
if isinstance(self.linear, Multipolygon)
else [self.shaped]))
>>> from gon.base import (Contour, Mix, Multipoint, Point, Polygon,
... Segment)
>>> mix = Mix(Multipoint([Point(3, 3)]),
... Segment(Point(6, 6), Point(6, 8)),
... Polygon(Contour([Point(0, 0), Point(6, 0), Point(6, 6),
... Point(0, 6)]),
... [Contour([Point(2, 2), Point(2, 4), Point(4, 4),
... Point(4, 2)])]))
>>> (mix.translate(1, 2)
... == Mix(Multipoint([Point(4, 5)]),
... Segment(Point(7, 8), Point(7, 10)),
... Polygon(Contour([Point(1, 2), Point(7, 2), Point(7, 8),
... Point(1, 8)]),
... [Contour([Point(3, 4), Point(3, 6), Point(5, 6),
... Point(5, 4)])])))
True
"""
return self._context.mix_cls(self.discrete.translate(step_x, step_y),
self.linear.translate(step_x, step_y),
self.shaped.translate(step_x, step_y))
[docs] def validate(self) -> None:
"""
Checks if the mix is valid.
Time complexity:
``O(elements_count * log elements_count)``
Memory complexity:
``O(elements_count)``
where
.. code-block:: python
elements_count = discrete_size + linear_size\
+ shaped_vertices_count
discrete_size = len(points)
linear_size = len(segments)
shaped_vertices_count = (sum(len(polygon.border.vertices)
+ sum(len(hole.vertices)
for hole in polygon.holes)
for polygon in polygons)
points = [] if self.discrete is EMPTY else self.discrete.points
segments = ([]
if self.linear is EMPTY
else ([self.linear]
if isinstance(self.linear, Segment)
else self.linear.segments))
polygons = ([]
if self.shaped is EMPTY
else (self.shaped.polygons
if isinstance(self.linear, Multipolygon)
else [self.shaped]))
>>> from gon.base import (Contour, Mix, Multipoint, Point, Polygon,
... Segment)
>>> mix = Mix(Multipoint([Point(3, 3)]),
... Segment(Point(6, 6), Point(6, 8)),
... Polygon(Contour([Point(0, 0), Point(6, 0), Point(6, 6),
... Point(0, 6)]),
... [Contour([Point(2, 2), Point(2, 4), Point(4, 4),
... Point(4, 2)])]))
>>> mix.validate()
"""
if (sum(component is not self._context.empty for component in
self._components)
< MIN_MIX_NON_EMPTY_COMPONENTS):
raise ValueError('At least {count} components should not be empty.'
.format(count=MIN_MIX_NON_EMPTY_COMPONENTS))
for component in self._components:
component.validate()
if (not self.discrete.disjoint(self.linear)
or not self.discrete.disjoint(self.shaped)):
raise ValueError('Discrete component should be disjoint '
'from other components.')
shaped_linear_relation = self.shaped.relate(self.linear)
if shaped_linear_relation in (Relation.CROSS, Relation.COMPONENT,
Relation.ENCLOSED, Relation.WITHIN):
raise ValueError('Linear component should not {} shaped component.'
.format('cross'
if (shaped_linear_relation
is Relation.CROSS)
else 'be subset of'))
elif (shaped_linear_relation is Relation.TOUCH
and any(polygon.border.relate(self.linear)
in (Relation.OVERLAP, Relation.COMPOSITE)
or any(hole.relate(self.linear)
in (Relation.OVERLAP, Relation.COMPOSITE)
for hole in polygon.holes)
for polygon in (
self.shaped.polygons
if isinstance(self.shaped,
self._context.multipolygon_cls)
else [self.shaped]
))):
raise ValueError('Linear component should not overlap '
'shaped component borders.')
def _relate_linear(self, other: Linear[Scalar]) -> Relation:
if self.shaped is self._context.empty:
linear_relation = self.linear.relate(other)
if linear_relation is Relation.DISJOINT:
discrete_relation = self.discrete.relate(other)
return (Relation.TOUCH
if discrete_relation is Relation.COMPOSITE
else discrete_relation)
elif linear_relation is Relation.COMPOSITE:
discrete_relation = self.discrete.relate(other)
return (linear_relation
if discrete_relation is linear_relation
else Relation.OVERLAP)
else:
return (Relation.COMPONENT
if linear_relation is Relation.EQUAL
else linear_relation)
else:
shaped_relation = self.shaped.relate(other)
if shaped_relation is Relation.DISJOINT:
linear_relation = self.linear.relate(other)
if linear_relation is Relation.DISJOINT:
discrete_relation = self.discrete.relate(other)
return (Relation.TOUCH
if discrete_relation is Relation.COMPOSITE
else discrete_relation)
elif linear_relation in (Relation.TOUCH,
Relation.CROSS,
Relation.COMPONENT):
return linear_relation
else:
return (Relation.COMPONENT
if linear_relation is Relation.EQUAL
else Relation.TOUCH)
elif (shaped_relation is Relation.TOUCH
or shaped_relation is Relation.CROSS):
rest_other = other - self.shaped
linear_relation = self.linear.relate(rest_other)
return (Relation.COMPONENT
if (linear_relation is Relation.EQUAL
or linear_relation is Relation.COMPONENT)
else shaped_relation)
else:
return shaped_relation
def _relate_mix(self, other: 'Mix[Scalar]') -> Relation:
if self.shaped is other.shaped is self._context.empty:
linear_components_relation = self.linear.relate(other.linear)
if linear_components_relation is Relation.DISJOINT:
return (linear_components_relation
if (self._relate_discrete(other.discrete)
is other._relate_discrete(self.discrete)
is linear_components_relation)
else Relation.TOUCH)
elif linear_components_relation is Relation.COMPOSITE:
discrete_relation = other._relate_discrete(self.discrete)
return (linear_components_relation
if discrete_relation is Relation.COMPONENT
else Relation.OVERLAP)
elif linear_components_relation is Relation.EQUAL:
other_discrete_relation = self.discrete.relate(other.discrete)
return (Relation.OVERLAP
if other_discrete_relation is Relation.DISJOINT
else other_discrete_relation)
elif linear_components_relation is Relation.COMPONENT:
other_discrete_relation = self._relate_discrete(other.discrete)
return (linear_components_relation
if other_discrete_relation is Relation.COMPONENT
else Relation.OVERLAP)
else:
return linear_components_relation
elif self.shaped is self._context.empty:
linear_relation = other._relate_linear(self.linear)
if linear_relation is Relation.CROSS:
return linear_relation
discrete_relation = other._relate_discrete(self.discrete)
if linear_relation is Relation.DISJOINT:
return (discrete_relation
if discrete_relation in (Relation.DISJOINT,
Relation.TOUCH,
Relation.CROSS)
else (Relation.TOUCH
if discrete_relation is Relation.COMPONENT
else Relation.CROSS))
elif linear_relation is Relation.TOUCH:
return (Relation.CROSS
if discrete_relation in (Relation.CROSS,
Relation.ENCLOSED,
Relation.WITHIN)
else linear_relation)
elif linear_relation is Relation.COMPONENT:
return (Relation.TOUCH
if discrete_relation is Relation.DISJOINT
else (discrete_relation
if (discrete_relation is Relation.TOUCH
or discrete_relation is Relation.CROSS)
else (Relation.COMPOSITE
if discrete_relation is Relation.COMPONENT
else Relation.ENCLOSES)))
else:
return (Relation.CROSS
if discrete_relation in (Relation.DISJOINT,
Relation.TOUCH,
Relation.CROSS)
else (Relation.COVER
if discrete_relation is Relation.WITHIN
else Relation.ENCLOSES))
elif other.shaped is self._context.empty:
other_linear_relation = self._relate_linear(other.linear)
if other_linear_relation is Relation.CROSS:
return other_linear_relation
other_discrete_relation = self._relate_discrete(other.discrete)
if other_linear_relation is Relation.DISJOINT:
return (other_discrete_relation
if other_discrete_relation in (Relation.DISJOINT,
Relation.TOUCH,
Relation.CROSS)
else (Relation.TOUCH
if other_discrete_relation is Relation.COMPONENT
else Relation.CROSS))
elif other_linear_relation is Relation.TOUCH:
return (Relation.CROSS
if other_discrete_relation in (Relation.CROSS,
Relation.ENCLOSED,
Relation.WITHIN)
else other_linear_relation)
elif other_linear_relation is Relation.COMPONENT:
return (Relation.TOUCH
if (other_discrete_relation is Relation.DISJOINT
or other_discrete_relation is Relation.TOUCH)
else (other_discrete_relation
if (other_discrete_relation is Relation.CROSS
or (other_discrete_relation
is Relation.COMPONENT))
else Relation.ENCLOSED))
elif other_linear_relation is Relation.ENCLOSED:
return (Relation.CROSS
if other_discrete_relation in (Relation.DISJOINT,
Relation.TOUCH,
Relation.CROSS)
else other_linear_relation)
else:
return (Relation.CROSS
if other_discrete_relation in (Relation.DISJOINT,
Relation.TOUCH,
Relation.CROSS)
else (Relation.ENCLOSED
if other_discrete_relation is Relation.COMPONENT
else other_linear_relation))
shaped_components_relation = self.shaped.relate(other.shaped)
if (shaped_components_relation is Relation.DISJOINT
or shaped_components_relation is Relation.TOUCH):
if self.linear is other.linear is self._context.empty:
other_discrete_relation = self._relate_discrete(other.discrete)
if other_discrete_relation is Relation.CROSS:
return other_discrete_relation
elif (other_discrete_relation is Relation.ENCLOSED
or other_discrete_relation is Relation.WITHIN):
return Relation.CROSS
else:
discrete_relation = other._relate_discrete(self.discrete)
if (discrete_relation
is other_discrete_relation
is Relation.DISJOINT):
return shaped_components_relation
elif discrete_relation is Relation.CROSS:
return discrete_relation
elif (discrete_relation is Relation.ENCLOSED
or discrete_relation is Relation.WITHIN):
return Relation.CROSS
else:
return Relation.TOUCH
elif self.linear is self._context.empty:
other_linear_relation = self._relate_linear(other.linear)
if other_linear_relation is Relation.CROSS:
return other_linear_relation
elif (other_linear_relation is Relation.ENCLOSED
or other_linear_relation is Relation.WITHIN):
return Relation.CROSS
else:
discrete_relation = other._relate_discrete(self.discrete)
if discrete_relation is Relation.CROSS:
return discrete_relation
elif (discrete_relation is Relation.ENCLOSED
or discrete_relation is Relation.WITHIN):
return Relation.CROSS
elif other.discrete is self._context.empty:
return (shaped_components_relation
if (discrete_relation
is other_linear_relation
is Relation.DISJOINT)
else Relation.TOUCH)
else:
other_discrete_relation = self._relate_discrete(
other.discrete
)
if other_discrete_relation is Relation.CROSS:
return other_discrete_relation
elif (other_discrete_relation is Relation.ENCLOSED
or other_discrete_relation is Relation.WITHIN):
return Relation.CROSS
elif (discrete_relation
is other_discrete_relation
is other_linear_relation
is Relation.DISJOINT):
return shaped_components_relation
else:
return Relation.TOUCH
elif other.linear is self._context.empty:
linear_relation = other._relate_linear(self.linear)
if linear_relation is Relation.CROSS:
return linear_relation
elif (linear_relation is Relation.ENCLOSED
or linear_relation is Relation.WITHIN):
return Relation.CROSS
else:
other_discrete_relation = self._relate_discrete(
other.discrete
)
if other_discrete_relation is Relation.CROSS:
return other_discrete_relation
elif (other_discrete_relation is Relation.ENCLOSED
or other_discrete_relation is Relation.WITHIN):
return Relation.CROSS
elif self.discrete is self._context.empty:
return (shaped_components_relation
if (linear_relation
is other_discrete_relation
is Relation.DISJOINT)
else Relation.TOUCH)
else:
discrete_relation = other._relate_discrete(
self.discrete
)
if discrete_relation is Relation.CROSS:
return discrete_relation
elif (discrete_relation is Relation.ENCLOSED
or discrete_relation is Relation.WITHIN):
return Relation.CROSS
elif (discrete_relation
is linear_relation
is other_discrete_relation
is Relation.DISJOINT):
return shaped_components_relation
else:
return Relation.TOUCH
else:
other_linear_relation = self._relate_linear(other.linear)
if other_linear_relation is Relation.CROSS:
return other_linear_relation
elif (other_linear_relation is Relation.ENCLOSED
or other_linear_relation is Relation.WITHIN):
return Relation.CROSS
else:
linear_relation = other._relate_linear(self.linear)
if linear_relation is Relation.CROSS:
return linear_relation
elif (linear_relation is Relation.ENCLOSED
or linear_relation is Relation.WITHIN):
return Relation.CROSS
elif self.discrete is self._context.empty:
other_discrete_relation = self._relate_discrete(
other.discrete
)
return (other_discrete_relation
if other_discrete_relation is Relation.CROSS
else
(Relation.CROSS
if (other_discrete_relation
is Relation.ENCLOSED
or other_discrete_relation
is Relation.WITHIN)
else (shaped_components_relation
if (other_discrete_relation
is linear_relation
is other_linear_relation
is Relation.DISJOINT)
else Relation.TOUCH)))
elif other.discrete is self._context.empty:
discrete_relation = other._relate_discrete(
self.discrete
)
return (discrete_relation
if discrete_relation is Relation.CROSS
else
(Relation.CROSS
if (discrete_relation is Relation.ENCLOSED
or discrete_relation is Relation.WITHIN)
else (shaped_components_relation
if (discrete_relation
is linear_relation
is other_linear_relation
is Relation.DISJOINT)
else Relation.TOUCH)))
else:
other_discrete_relation = self._relate_discrete(
other.discrete
)
if other_discrete_relation is Relation.CROSS:
return other_discrete_relation
elif (other_discrete_relation is Relation.ENCLOSED
or other_discrete_relation is Relation.WITHIN):
return Relation.CROSS
else:
discrete_relation = other._relate_discrete(
self.discrete
)
return (discrete_relation
if discrete_relation is Relation.CROSS
else (Relation.CROSS
if (discrete_relation
is Relation.ENCLOSED
or discrete_relation
is Relation.WITHIN)
else
(shaped_components_relation
if (discrete_relation
is linear_relation
is other_discrete_relation
is other_linear_relation
is Relation.DISJOINT)
else Relation.TOUCH)))
elif shaped_components_relation in (Relation.COVER,
Relation.ENCLOSES,
Relation.COMPOSITE):
if self.linear is self._context.empty:
discrete_relation = (other._relate_discrete(self.discrete)
.complement)
return (shaped_components_relation
if discrete_relation is shaped_components_relation
else (Relation.ENCLOSES
if discrete_relation in (Relation.COVER,
Relation.ENCLOSES,
Relation.COMPOSITE)
else Relation.OVERLAP))
else:
linear_relation = other._relate_linear(self.linear).complement
if linear_relation is shaped_components_relation:
if self.discrete is self._context.empty:
return shaped_components_relation
else:
discrete_relation = other._relate_discrete(
self.discrete
).complement
return (shaped_components_relation
if (discrete_relation
is shaped_components_relation)
else
(Relation.ENCLOSES
if discrete_relation in (Relation.COVER,
Relation.ENCLOSES,
Relation.COMPOSITE)
else Relation.OVERLAP))
elif linear_relation in (Relation.COVER,
Relation.ENCLOSES,
Relation.COMPOSITE):
if self.discrete is self._context.empty:
return Relation.ENCLOSES
else:
discrete_relation = other._relate_discrete(
self.discrete
).complement
return (Relation.ENCLOSES
if discrete_relation in (Relation.COVER,
Relation.ENCLOSES,
Relation.COMPOSITE)
else Relation.OVERLAP)
else:
return Relation.OVERLAP
elif shaped_components_relation is Relation.EQUAL:
linear_components_relation = self.linear.relate(other.linear)
if self.linear is other.linear is self._context.empty:
discrete_components_relation = self.discrete.relate(
other.discrete
)
return (
shaped_components_relation
if (self.discrete is other.discrete is self._context.empty
or discrete_components_relation is Relation.EQUAL)
else
(discrete_components_relation
if (discrete_components_relation is Relation.COMPOSITE
or discrete_components_relation is Relation.COMPONENT)
else Relation.OVERLAP)
)
elif self.linear is self._context.empty:
discrete_components_relation = other._relate_discrete(
self.discrete
)
return (
Relation.COMPOSITE
if (discrete_components_relation is Relation.EQUAL
or discrete_components_relation is Relation.COMPONENT)
else Relation.OVERLAP
)
elif other.linear is self._context.empty:
discrete_components_relation = self._relate_discrete(
other.discrete
)
return (
Relation.COMPONENT
if (discrete_components_relation is Relation.EQUAL
or discrete_components_relation is Relation.COMPONENT)
else Relation.OVERLAP
)
elif linear_components_relation is Relation.COMPOSITE:
discrete_components_relation = other._relate_discrete(
self.discrete
)
return (
linear_components_relation
if (self.discrete is self._context.empty
or discrete_components_relation is Relation.EQUAL
or discrete_components_relation is Relation.COMPONENT)
else Relation.OVERLAP
)
elif linear_components_relation is Relation.EQUAL:
discrete_components_relation = self.discrete.relate(
other.discrete
)
return (
shaped_components_relation
if (self.discrete is other.discrete is self._context.empty
or discrete_components_relation is Relation.EQUAL)
else
(Relation.COMPOSITE
if self.discrete is self._context.empty
else
(Relation.COMPONENT
if other.discrete is self._context.empty
else
(discrete_components_relation
if
(discrete_components_relation is Relation.COMPONENT
or discrete_components_relation is Relation.COMPOSITE)
else Relation.OVERLAP)))
)
elif linear_components_relation is Relation.COMPONENT:
discrete_components_relation = self._relate_discrete(
other.discrete
)
return (
linear_components_relation
if (other.discrete is self._context.empty
or discrete_components_relation is Relation.EQUAL
or discrete_components_relation is Relation.COMPONENT)
else Relation.OVERLAP
)
else:
return Relation.OVERLAP
elif shaped_components_relation in (Relation.COMPONENT,
Relation.ENCLOSED,
Relation.WITHIN):
if other.linear is self._context.empty:
discrete_relation = self._relate_discrete(other.discrete)
return (shaped_components_relation
if discrete_relation is shaped_components_relation
else (Relation.ENCLOSED
if discrete_relation in (Relation.COMPONENT,
Relation.ENCLOSED,
Relation.WITHIN)
else Relation.OVERLAP))
else:
linear_relation = self._relate_linear(other.linear)
if linear_relation is shaped_components_relation:
if other.discrete is self._context.empty:
return shaped_components_relation
else:
discrete_relation = self._relate_discrete(
other.discrete
)
return (shaped_components_relation
if (discrete_relation
is shaped_components_relation)
else
(Relation.ENCLOSED
if discrete_relation in (Relation.COMPONENT,
Relation.ENCLOSED,
Relation.WITHIN)
else Relation.OVERLAP))
elif linear_relation in (Relation.COMPONENT,
Relation.ENCLOSED,
Relation.WITHIN):
if other.discrete is self._context.empty:
return Relation.ENCLOSED
else:
discrete_relation = self._relate_discrete(
other.discrete
)
return (Relation.ENCLOSED
if discrete_relation in (Relation.COMPONENT,
Relation.ENCLOSED,
Relation.WITHIN)
else Relation.OVERLAP)
else:
return Relation.OVERLAP
else:
return shaped_components_relation
def _relate_discrete(self, other: Multipoint[Scalar]) -> Relation:
if self.shaped is self._context.empty:
linear_relation = self.linear.relate(other)
if linear_relation is Relation.DISJOINT:
discrete_relation = self.discrete.relate(other)
return (discrete_relation
if discrete_relation is Relation.DISJOINT
else (Relation.COMPONENT
if (discrete_relation is Relation.COMPONENT
or discrete_relation is Relation.EQUAL)
else Relation.TOUCH))
elif linear_relation is Relation.TOUCH:
rest_other = other - self.linear
discrete_relation = self.discrete.relate(rest_other)
return (Relation.COMPONENT
if (discrete_relation is Relation.EQUAL
or discrete_relation is Relation.COMPONENT)
else linear_relation)
else:
return linear_relation
else:
shaped_relation = self.shaped.relate(other)
if shaped_relation in (Relation.COMPONENT,
Relation.ENCLOSED,
Relation.WITHIN):
return shaped_relation
elif (shaped_relation is Relation.TOUCH
or shaped_relation is Relation.CROSS):
rest_other = other - self.shaped
if self.linear is self._context.empty:
discrete_relation = self.discrete.relate(rest_other)
return (Relation.COMPONENT
if (discrete_relation is Relation.EQUAL
or discrete_relation is Relation.COMPONENT)
else shaped_relation)
else:
linear_relation = self.linear.relate(rest_other)
if linear_relation is Relation.DISJOINT:
discrete_relation = self.discrete.relate(rest_other)
return ((Relation.COMPONENT
if shaped_relation is Relation.TOUCH
else Relation.ENCLOSED)
if (discrete_relation is Relation.COMPONENT
or discrete_relation is Relation.EQUAL)
else shaped_relation)
elif linear_relation is Relation.TOUCH:
rest_other -= self.linear
discrete_relation = self.discrete.relate(rest_other)
return (Relation.COMPONENT
if (discrete_relation is Relation.COMPONENT
or discrete_relation is Relation.EQUAL)
else shaped_relation)
else:
return (Relation.COMPONENT
if shaped_relation is Relation.TOUCH
else Relation.ENCLOSED)
else:
linear_relation = self.linear.relate(other)
if linear_relation is Relation.DISJOINT:
discrete_relation = self.discrete.relate(other)
return (shaped_relation
if discrete_relation is Relation.DISJOINT
else (Relation.COMPONENT
if (discrete_relation is Relation.COMPONENT
or discrete_relation is Relation.EQUAL)
else Relation.TOUCH))
elif linear_relation is Relation.TOUCH:
rest_other = other - self.linear
discrete_relation = self.discrete.relate(rest_other)
return (shaped_relation
if discrete_relation is Relation.DISJOINT
else (Relation.COMPONENT
if (discrete_relation is Relation.COMPONENT
or discrete_relation is Relation.EQUAL)
else Relation.TOUCH))
else:
return linear_relation
def _relate_shaped(self, other: Shaped[Scalar]) -> Relation:
if self.shaped is self._context.empty:
linear_relation = self.linear.relate(other)
if (linear_relation is Relation.DISJOINT
or linear_relation is Relation.TOUCH):
discrete_relation = self.discrete.relate(other)
return (linear_relation
if discrete_relation is Relation.DISJOINT
else (discrete_relation
if (discrete_relation is Relation.TOUCH
or discrete_relation is Relation.CROSS)
else (Relation.TOUCH
if (discrete_relation
is Relation.COMPOSITE)
else Relation.CROSS)))
elif (linear_relation is Relation.COVER
or linear_relation is Relation.ENCLOSES):
discrete_relation = self.discrete.relate(other)
return (Relation.CROSS
if (discrete_relation is Relation.DISJOINT
or discrete_relation is Relation.TOUCH)
else (discrete_relation
if (discrete_relation is linear_relation
or discrete_relation is Relation.CROSS)
else Relation.ENCLOSES))
elif linear_relation is Relation.COMPOSITE:
discrete_relation = self.discrete.relate(other)
return (Relation.TOUCH
if discrete_relation is Relation.DISJOINT
else (discrete_relation
if (discrete_relation is Relation.TOUCH
or discrete_relation is Relation.CROSS)
else (linear_relation
if discrete_relation is linear_relation
else Relation.CROSS)))
else:
return linear_relation
else:
shaped_relation = self.shaped.relate(other)
if shaped_relation is Relation.DISJOINT:
linear_relation = self.linear.relate(other)
if linear_relation is Relation.DISJOINT:
discrete_relation = self.discrete.relate(other)
return (discrete_relation
if discrete_relation in (Relation.DISJOINT,
Relation.TOUCH,
Relation.CROSS)
else (Relation.TOUCH
if discrete_relation is Relation.COMPOSITE
else Relation.CROSS))
elif (linear_relation is Relation.TOUCH
or linear_relation is Relation.COMPOSITE):
discrete_relation = self.discrete.relate(other)
return (Relation.TOUCH
if discrete_relation in (Relation.DISJOINT,
Relation.TOUCH,
Relation.COMPOSITE)
else Relation.CROSS)
else:
return Relation.CROSS
elif shaped_relation is Relation.TOUCH:
linear_relation = self.linear.relate(other)
if linear_relation in (Relation.DISJOINT,
Relation.TOUCH,
Relation.COMPOSITE):
discrete_relation = self.discrete.relate(other)
return (shaped_relation
if discrete_relation in (Relation.DISJOINT,
Relation.TOUCH,
Relation.COMPOSITE)
else Relation.CROSS)
else:
return Relation.CROSS
elif (shaped_relation is Relation.COVER
or shaped_relation is Relation.COMPOSITE):
if self.linear is self._context.empty:
discrete_relation = self.discrete.relate(other)
return (Relation.OVERLAP
if discrete_relation in (Relation.DISJOINT,
Relation.TOUCH,
Relation.CROSS)
else (shaped_relation
if discrete_relation is shaped_relation
else Relation.ENCLOSES))
else:
linear_relation = self.linear.relate(other)
if linear_relation in (Relation.DISJOINT,
Relation.TOUCH,
Relation.CROSS):
return Relation.OVERLAP
elif self.discrete is self._context.empty:
return (shaped_relation
if linear_relation is shaped_relation
else Relation.ENCLOSES)
else:
discrete_relation = self.discrete.relate(other)
return (Relation.OVERLAP
if discrete_relation in (Relation.DISJOINT,
Relation.TOUCH,
Relation.CROSS)
else (shaped_relation
if (discrete_relation
is linear_relation
is shaped_relation)
else Relation.ENCLOSES))
elif shaped_relation is Relation.ENCLOSES:
if self.linear is self._context.empty:
discrete_relation = self.discrete.relate(other)
return (Relation.OVERLAP
if discrete_relation in (Relation.DISJOINT,
Relation.TOUCH,
Relation.CROSS)
else Relation.ENCLOSES)
else:
linear_relation = self.linear.relate(other)
if linear_relation in (Relation.DISJOINT,
Relation.TOUCH,
Relation.CROSS):
return Relation.OVERLAP
elif self.discrete is self._context.empty:
return shaped_relation
else:
discrete_relation = self.discrete.relate(other)
return (Relation.OVERLAP
if discrete_relation in (Relation.DISJOINT,
Relation.TOUCH,
Relation.CROSS)
else Relation.ENCLOSES)
else:
return (Relation.COMPONENT
if shaped_relation is Relation.EQUAL
else shaped_relation)