from typing import Optional
from clipping.planar import (intersect_segments,
subtract_segments,
symmetric_subtract_segments,
unite_segments)
from ground.hints import Scalar
from orient.planar import (point_in_segment,
segment_in_segment)
from reprit.base import generate_repr
from .angle import Angle
from .compound import (Compound,
Linear,
Location,
Relation)
from .geometry import Geometry
from .iterable import non_negative_min
from .multipoint import Multipoint
from .packing import pack_mix
from .point import Point
from .utils import relate_multipoint_to_linear_compound
class Segment(Compound[Scalar], Linear[Scalar]):
__slots__ = '_endpoints', '_end', '_start'
[docs] def __init__(self,
start: Point[Scalar],
end: Point[Scalar]) -> None:
"""
Initializes segment.
Time complexity:
``O(1)``
Memory complexity:
``O(1)``
"""
self._start, self._end = self._endpoints = start, end
__repr__ = generate_repr(__init__)
[docs] def __and__(self, other: Compound[Scalar]) -> Compound[Scalar]:
"""
Returns intersection of the segment with the other geometry.
Time complexity:
``O(1)``
Memory complexity:
``O(1)``
>>> from gon.base import Point, Segment
>>> segment = Segment(Point(0, 0), Point(2, 0))
>>> segment & segment == segment
True
"""
return (intersect_segments(self, other,
context=self._context)
if isinstance(other, Segment)
else NotImplemented)
__rand__ = __and__
[docs] def __contains__(self, point: Point[Scalar]) -> bool:
"""
Checks if the segment contains the point.
Time complexity:
``O(1)``
Memory complexity:
``O(1)``
>>> from gon.base import Point, Segment
>>> segment = Segment(Point(0, 0), Point(2, 0))
>>> segment.start in segment
True
>>> segment.end in segment
True
"""
return bool(self.locate(point))
[docs] def __eq__(self, other: 'Segment[Scalar]') -> bool:
"""
Checks if the segment is equal to the other.
Time complexity:
``O(1)``
Memory complexity:
``O(1)``
>>> from gon.base import Point, Segment
>>> segment = Segment(Point(0, 0), Point(2, 0))
>>> segment == segment
True
>>> segment == Segment(Point(2, 0), Point(0, 0))
True
>>> segment == Segment(Point(0, 0), Point(1, 0))
False
>>> segment == Segment(Point(0, 0), Point(0, 2))
False
"""
return (self is other
or (self.start == other.start and self.end == other.end
or self.start == other.end and self.end == other.start
if isinstance(other, Segment)
else NotImplemented))
[docs] def __ge__(self, other: Compound[Scalar]) -> bool:
"""
Checks if the segment is a superset of the other geometry.
Time complexity:
``O(1)``
Memory complexity:
``O(1)``
>>> from gon.base import Point, Segment
>>> segment = Segment(Point(0, 0), Point(2, 0))
>>> segment >= segment
True
>>> segment >= Segment(Point(2, 0), Point(0, 0))
True
>>> segment >= Segment(Point(0, 0), Point(1, 0))
True
>>> segment >= Segment(Point(0, 0), Point(0, 2))
False
"""
return (other is self._context.empty
or self == other
or ((self.relate(other) is Relation.COMPONENT
if isinstance(other, (Multipoint, Segment))
# segment cannot be superset of contour or shaped
else False)
if isinstance(other, Compound)
else NotImplemented))
[docs] def __gt__(self, other: Compound[Scalar]) -> bool:
"""
Checks if the segment is a strict superset of the other geometry.
Time complexity:
``O(1)``
Memory complexity:
``O(1)``
>>> from gon.base import Point, Segment
>>> segment = Segment(Point(0, 0), Point(2, 0))
>>> segment > segment
False
>>> segment > Segment(Point(2, 0), Point(0, 0))
False
>>> segment > Segment(Point(0, 0), Point(1, 0))
True
>>> segment > Segment(Point(0, 0), Point(0, 2))
False
"""
return (other is self._context.empty
or self != other
and ((self.relate(other) is Relation.COMPONENT
if isinstance(other, (Multipoint, Segment))
# segment cannot be strict superset of contour or shaped
else False)
if isinstance(other, Compound)
else NotImplemented))
[docs] def __hash__(self) -> int:
"""
Returns hash value of the segment.
Time complexity:
``O(1)``
Memory complexity:
``O(1)``
>>> from gon.base import Point, Segment
>>> segment = Segment(Point(0, 0), Point(2, 0))
>>> hash(segment) == hash(segment)
True
>>> hash(segment) == hash(Segment(Point(2, 0), Point(0, 0)))
True
"""
return hash(frozenset(self._endpoints))
[docs] def __le__(self, other: Compound[Scalar]) -> bool:
"""
Checks if the segment is a subset of the other geometry.
Time complexity:
``O(1)``
Memory complexity:
``O(1)``
>>> from gon.base import Point, Segment
>>> segment = Segment(Point(0, 0), Point(2, 0))
>>> segment <= segment
True
>>> segment <= Segment(Point(2, 0), Point(0, 0))
True
>>> segment <= Segment(Point(0, 0), Point(1, 0))
False
>>> segment <= Segment(Point(0, 0), Point(0, 2))
False
"""
return (self == other
or not isinstance(other, Multipoint)
and (self.relate(other) in (Relation.EQUAL, Relation.COMPOSITE)
if isinstance(other, Linear)
else NotImplemented))
[docs] def __lt__(self, other: Compound[Scalar]) -> bool:
"""
Checks if the segment is a strict subset of the other geometry.
Time complexity:
``O(1)``
Memory complexity:
``O(1)``
>>> from gon.base import Point, Segment
>>> segment = Segment(Point(0, 0), Point(2, 0))
>>> segment < segment
False
>>> segment < Segment(Point(2, 0), Point(0, 0))
False
>>> segment < Segment(Point(0, 0), Point(1, 0))
False
>>> segment < Segment(Point(0, 0), Point(0, 2))
False
"""
return (self != other
and not isinstance(other, Multipoint)
and (self.relate(other) is Relation.COMPOSITE
if isinstance(other, Linear)
else NotImplemented))
[docs] def __or__(self, other: Compound) -> Compound:
"""
Returns union of the segment with the other geometry.
Time complexity:
``O(1)``
Memory complexity:
``O(1)``
>>> from gon.base import Point, Segment
>>> segment = Segment(Point(0, 0), Point(2, 0))
>>> segment | segment == segment
True
"""
context = self._context
return (pack_mix(other - self, self, context.empty, context.empty,
context.mix_cls)
if isinstance(other, Multipoint)
else (unite_segments(self, other,
context=context)
if isinstance(other, Segment)
else NotImplemented))
__ror__ = __or__
[docs] def __sub__(self, other: Compound[Scalar]) -> Compound[Scalar]:
"""
Returns difference of the segment with the other geometry.
Time complexity:
``O(1)``
Memory complexity:
``O(1)``
>>> from gon.base import EMPTY, Point, Segment
>>> segment = Segment(Point(0, 0), Point(2, 0))
>>> segment - segment is EMPTY
True
"""
return (self
if isinstance(other, Multipoint)
else (subtract_segments(self, other,
context=self._context)
if isinstance(other, Segment)
else NotImplemented))
[docs] def __xor__(self, other: Compound[Scalar]) -> Compound[Scalar]:
"""
Returns symmetric difference of the segment with the other geometry.
Time complexity:
``O(1)``
Memory complexity:
``O(1)``
>>> from gon.base import EMPTY, Point, Segment
>>> segment = Segment(Point(0, 0), Point(2, 0))
>>> segment ^ segment is EMPTY
True
"""
context = self._context
return (pack_mix(other - self, self, context.empty, context.empty,
context.mix_cls)
if isinstance(other, Multipoint)
else (symmetric_subtract_segments(self, other,
context=context)
if isinstance(other, Segment)
else NotImplemented))
__rxor__ = __xor__
@property
def centroid(self) -> Point[Scalar]:
"""
Returns centroid of the segment.
Time complexity:
``O(1)``
Memory complexity:
``O(1)``
>>> from gon.base import Point, Segment
>>> segment = Segment(Point(0, 0), Point(2, 0))
>>> segment.centroid == Point(1, 0)
True
"""
return self._context.segment_centroid(self)
@property
def end(self) -> Point[Scalar]:
"""
Returns end of the segment.
Time complexity:
``O(1)``
Memory complexity:
``O(1)``
>>> from gon.base import Point, Segment
>>> segment = Segment(Point(0, 0), Point(2, 0))
>>> segment.end == Point(2, 0)
True
"""
return self._end
@property
def is_horizontal(self) -> bool:
"""
Checks if the segment is horizontal.
Time complexity:
``O(1)``
Memory complexity:
``O(1)``
>>> from gon.base import Point, Segment
>>> segment = Segment(Point(0, 0), Point(2, 0))
>>> segment.is_horizontal
True
"""
return self.start.y == self.end.y
@property
def is_vertical(self) -> bool:
"""
Checks if the segment is vertical.
Time complexity:
``O(1)``
Memory complexity:
``O(1)``
>>> from gon.base import Point, Segment
>>> segment = Segment(Point(0, 0), Point(2, 0))
>>> segment.is_vertical
False
"""
return self.start.x == self.end.x
@property
def length(self) -> Scalar:
"""
Returns length of the segment.
Time complexity:
``O(1)``
Memory complexity:
``O(1)``
>>> from gon.base import Point, Segment
>>> segment = Segment(Point(0, 0), Point(2, 0))
>>> segment.length == 2
True
"""
return self._context.segment_length(self)
@property
def start(self) -> Point[Scalar]:
"""
Returns start of the segment.
Time complexity:
``O(1)``
Memory complexity:
``O(1)``
>>> from gon.base import Point, Segment
>>> segment = Segment(Point(0, 0), Point(2, 0))
>>> segment.start == Point(0, 0)
True
"""
return self._start
[docs] def distance_to(self, other: Geometry[Scalar]) -> Scalar:
"""
Returns distance between the segment and the other geometry.
Time complexity:
``O(1)``
Memory complexity:
``O(1)``
>>> from gon.base import Point, Segment
>>> segment = Segment(Point(0, 0), Point(2, 0))
>>> segment.distance_to(segment) == 0
True
"""
if isinstance(other, Point):
return self._context.sqrt(
self._context.segment_point_squared_distance(self, other)
)
elif isinstance(other, Multipoint):
return non_negative_min(
self._context.sqrt(
self._context.segment_point_squared_distance(
self, point
))
for point in other.points
)
elif isinstance(other, Segment):
return self._context.sqrt(
self._context.segments_squared_distance(self, other)
)
else:
return other.distance_to(self)
[docs] def locate(self, point: Point[Scalar]) -> Location:
"""
Finds location of the point relative to the segment.
Time complexity:
``O(1)``
Memory complexity:
``O(1)``
>>> from gon.base import Point, Segment
>>> segment = Segment(Point(0, 0), Point(2, 0))
>>> segment.locate(segment.start) is Location.BOUNDARY
True
>>> segment.locate(segment.end) is Location.BOUNDARY
True
"""
return point_in_segment(point, self,
context=self._context)
[docs] def relate(self, other: Compound[Scalar]) -> Relation:
"""
Finds relation between the segment and the other geometry.
Time complexity:
``O(1)``
Memory complexity:
``O(1)``
>>> from gon.base import Point, Segment
>>> segment = Segment(Point(0, 0), Point(2, 0))
>>> segment.relate(segment) is Relation.EQUAL
True
"""
return (relate_multipoint_to_linear_compound(other, self)
if isinstance(other, Multipoint)
else (segment_in_segment(other, self,
context=self._context)
if isinstance(other, Segment)
else other.relate(self).complement))
[docs] def rotate(self,
angle: Angle,
point: Optional[Point[Scalar]] = None) -> 'Segment[Scalar]':
"""
Rotates the segment by given angle around given point.
Time complexity:
``O(1)``
Memory complexity:
``O(1)``
>>> from gon.base import Angle, Point, Segment
>>> segment = Segment(Point(0, 0), Point(2, 0))
>>> segment.rotate(Angle(1, 0)) == segment
True
>>> (segment.rotate(Angle(0, 1), Point(1, 1))
... == Segment(Point(2, 0), Point(2, 2)))
True
"""
return (self._context.rotate_segment_around_origin(self, angle.cosine,
angle.sine)
if point is None
else self._context.rotate_segment(self, angle.cosine,
angle.sine, point))
[docs] def scale(self,
factor_x: Scalar,
factor_y: Optional[Scalar] = None) -> Compound[Scalar]:
"""
Scales the segment by given factor.
Time complexity:
``O(1)``
Memory complexity:
``O(1)``
>>> from gon.base import Point, Segment
>>> segment = Segment(Point(0, 0), Point(2, 0))
>>> segment.scale(1) == segment.scale(1, 2) == segment
True
"""
return self._context.scale_segment(
self, factor_x, factor_x if factor_y is None else factor_y
)
[docs] def translate(self,
step_x: Scalar,
step_y: Scalar) -> 'Segment[Scalar]':
"""
Translates the segment by given step.
Time complexity:
``O(1)``
Memory complexity:
``O(1)``
>>> from gon.base import Point, Segment
>>> segment = Segment(Point(0, 0), Point(2, 0))
>>> segment.translate(1, 2) == Segment(Point(1, 2), Point(3, 2))
True
"""
return self._context.translate_segment(self, step_x, step_y)
[docs] def validate(self) -> None:
"""
Checks if endpoints are valid and unequal.
Time complexity:
``O(1)``
Memory complexity:
``O(1)``
>>> from gon.base import Point, Segment
>>> segment = Segment(Point(0, 0), Point(2, 0))
>>> segment.validate()
"""
self.start.validate()
self.end.validate()
if self.start == self.end:
raise ValueError('Segment is degenerate.')