diff --git a/tdcgal/plane/objects.py b/tdcgal/plane/objects.py index 6133dbd..d9880cc 100644 --- a/tdcgal/plane/objects.py +++ b/tdcgal/plane/objects.py @@ -4,7 +4,15 @@ from sympy import symbols, solve from loguru import logger -from ..cgal_bindings.plane import Point_2, Segment_2, Line_2, squared_distance +from ..cgal_bindings.plane import ( + Point_2, + Segment_2, + Vector_2, + Ray_2, + Direction_2, + Line_2, + squared_distance, +) def point_maker(pre_point): @@ -21,22 +29,25 @@ class Shape2D(object): def __init__(self, **kwargs): self.color = kwargs.get("color", "white") + # 计算self到另一个Shape2D的距离,返回float + def distance( + self, target: [Tuple[float, float], Point2D, Line2D, Segment2D, Ray2D] + ) -> float: + if isinstance(target, tuple): + target = point_maker(target) + return (squared_distance(self, target)) ** 0.5 + class Point2D(Shape2D, Point_2): def __init__(self, x: float, y: float, **kwargs): Point_2.__init__(self, x, y) Shape2D.__init__(self, **kwargs) - # 计算点到另一点的距离 - def distance_to_point(self, point): - point = point_maker(point) - return (squared_distance(self, point)) ** 0.5 - # 重载==运算符,判断两个Point2D实例是否为同一点 - def __eq__(self, other): + def __eq__(self, other: [Tuple[float, float], Point2D]) -> bool: self.point == point_maker(other) - - def __repr__(self): + + def __repr__(self) -> str: return f"Point2D(x: {self.x}, y: {self.y})" @@ -48,15 +59,16 @@ class Segment2D(Shape2D, Segment_2): super(Segment_2, self).__init__(point1, point2) # 计算线段的长度 - def length(self): - return self.squared_length() ** 0.5 + def length(self) -> float: + return self._squared_length() ** 0.5 # 重载==运算符,判断两个Segment2D实例是否为同一条线段 - def __eq__(self, other) -> bool: + def __eq__(self, other: Segment2D) -> bool: return self == other # 重载`in`运算符,判断点是否在线段上 - def __contains__(self, point) -> bool: + def __contains__(self, point: [Tuple[float, float], Point2D]) -> bool: + point = point_maker(point) return self.has_on(point) # 重载`repr`方法,返回Segment2D的字符串表示 @@ -65,251 +77,129 @@ class Segment2D(Shape2D, Segment_2): # 线段的起点`source` @property - def source(self): + def source(self) -> Point2D: return self._source() - + # 线段的终点`target` @property - def target(self): + def target(self) -> Point2D: return self._target() +class Ray2D(Shape2D, Ray_2): + def __init__( + self, + source: [Tuple[float, float], Point2D], + point: [Tuple[float, float], Point2D] = None, + vec: Vector2D = None, + direction: Direction2D = None, + line: Line2D = None, + **kwargs, + ): + source = point_maker(source) + if point is not None: + point = point_maker(point) + Ray_2.__init__(self, source, point) + elif vec is not None: + Ray_2.__init__(self, source, vec) + elif direction is not None: + Ray_2.__init__(self, source, direction) + elif line is not None: + Ray_2.__init__(self, source, line) + else: + raise ValueError("Invalid input for Ray2D") + Shape2D.__init__(self, **kwargs) + + # 返回射线起点 + @property + def source(self) -> Point2D: + return self._source() + + + +class Direction2D(Shape2D, Direction_2): + pass + + +class Vector2D(Shape2D, Vector_2): + pass + + # 直线类 -class Line(Shape2D, Line_2): - def __init__(self, point1, point2, **kwargs): - super(Line, self).__init__(**kwargs) - self.point1 = point_maker(point1) - self.point2 = point_maker(point2) - if self.point1 == self.point2: - raise ValueError("Invalid input for Line: point1 == point2") - self._a = self.point2.y - self.point1.y - self._b = self.point1.x - self.point2.x - self._c = self.point2.x * self.point1.y - self.point1.x * self.point2.y +class Line2D(Shape2D, Line_2): + def __init__( + self, + init_from: Dict, + **kwargs, + ): + """直线Line2D的初始化方法 - if isclose(self.point1.x, self.point2.x): - self._slope = float("inf") - self._y_intercept = float("inf") - else: - self._slope = -self._a / self._b - self._y_intercept = -self._c / self._b - self._x, self._y = symbols("x y") - self._expression = self._a * self._x + self._b * self._y + self._c - self._length = ( - (self.point1.x - self.point2.x) ** 2 + (self.point1.y - self.point2.y) ** 2 - ) ** 0.5 - - def contains(self, point, allow_on_extended=False): - """"""判断点`point`是否在线段上 Args: - point (Point2D or tuple): 需要判断的点 - allow_on_extended (bool, optional): 是否允许点在线段的延长线上. Defaults to False. + init_from (Dict): 直线Line2D的初始化参数,可以是以下几种形式: + - "coefficient": 直线一般式方程的参数A, B, C + - "points": 两个端点 + - "point_and_direction": 点和方向 + - "point_and_vector": 点和向量 + - "segment": 线段 + - "ray": 射线 - Returns: - _type_: boolean - """""" - point = point_maker(point) + example: + Line2D(init_from={"coefficient": (1, 2, 3)}) + Line2D(init_from={"points": ((1, 2), (3, 4)}) + Line2D(init_from={"point_and_direction": (1, 2), Direction2D(***)}) + Line2D(init_from={"point_and_vector": (1, 2), Vector2D(***)}) + Line2D(init_from={"segment": Segment2D((1, 2), (3, 4))}) + Line2D(init_from={"ray": (1, 2), Ray2D((***))}) + """ + Shape2D.__init__(self, **kwargs) + if "points" in init_from: + point1 = point_maker(point1) + point2 = point_maker(point2) + Line_2.__init__(self, point1, point2) + if "coefficient" in init_from: + Line_2.__init__(self, a, b, c) + if "point_and_direction" in init_from: + Line_2.__init__(self, point1, direction) + if "point_and_vector" in init_from: + point1 = point_maker(point1) + Line_2.__init__(self, point1, vec) + if "segment" in init_from: + Line_2.__init__(self, seg) + if "ray" in init_from: + Line_2.__init__(self, ray) - f = self.function - if allow_on_extended: - if isclose(self._b, 0.0): - return isclose(point.x, self.point1.x) - return isclose(f(point.x), point.y) - - if isclose(self._b, 0.0): - return isclose(point.x, self.point1.x) and ( - point.y >= min(self.point1.y, self.point2.y) - and point.y <= max(self.point1.y, self.point2.y) - ) - return isclose(f(point.x), point.y) and ( - ( - point.x >= min(self.point1.x, self.point2.x) - and point.x <= max(self.point1.x, self.point2.x) - ) - ) - - # 通过关键字`in`判断点是否在线段上 - def __contains__(self, point): - return self.contains(point) - - # 计算点`point`到线段的垂足 - def foot_point(self, point): - point = point_maker(point) - if self.contains(point, allow_on_extended=True): - return point + self._x, self._y = symbols("x y") + if self.is_degenerate(): + self._expression = None else: - denominator = self._a**2 + self._b**2 - x = ( - (self._b**2) * point.x - - self._a * self._b * point.y - - self._a * self._c - ) / denominator - y = ( - (self._a**2) * point.y - - self._a * self._b * point.x - - self._b * self._c - ) / denominator - return Point2D(x, y) + self._expression = self.a * self._x + self.b * self._y + self.c - # 计算点`point`到线段的距离 - def point_distance(self, point, is_to_line=False): - point = point_maker(point) - if self.contains(point): - return 0 - else: - dis_to_line = ( - abs(self._expression.evalf(subs={self._x: point.x, self._y: point.y})) - / (self._a**2 + self._b**2) ** 0.5 - ) - if is_to_line: - return dis_to_line - if self.foot_point(point) not in self: - return min( - self.point1.distance_to_point(point), - self.point2.distance_to_point(point), - ) - return dis_to_line - - # 该线段所在直线的纵截距 @property - def y_intercept(self): - return self._y_intercept + def a(self) -> float: + return self._a() - # 该线段的斜率 @property - def slope(self): - return self._slope + def b(self) -> float: + return self._b() - # 该线段所在直线的函数方程 @property - def function(self): - def f(x): - expr = self._expression.subs(self._x, x) - res = solve(expr, self._y) - if len(res) == 1: - return res[0].evalf() - else: - return None - return f + def c(self) -> float: + return self._c() - # 该线段的函数式 + # 返回该直线一般式方程文本,形如`ax+by+c=0` @property def expression(self): return f"{str(self._expression)} = 0" - @property - # 该线段的长度 - def length(self): - return self._length - - """""" - # 该线段与另一线段的交点 - def intersection(self, other): - if isinstance(other, Line): - if self.slope() == other.slope(): - return None - else: - x = ( - self.slope() * other.point1.x - - self.point1.y - + other.slope() * self.point1.x - - other.point1.y - ) / (self.slope() - other.slope()) - y = self.slope() * (x - self.point1.x) + self.point1.y - return Point2D(x, y) - else: - raise ValueError("Invalid input for Line") - """""" - -""" -class Rectangle(Shape2D): - def __init__(self, point1, point2, **kwargs): - super(Rectangle, self).__init__(**kwargs) - p1 = point_maker(point1) - p2 = point_maker(point2) - self.point1 = point_maker(p1) - self.point2 = point_maker((point1.x, point2.y)) - self.point3 = point_maker(point2) - self.point4 = point_maker((point2.x, point1.y)) - - # 该矩形的四条边 - def sides(self): - return [ - Line(self.point1, self.point2), - Line(self.point2, self.point3), - Line(self.point3, self.point4), - Line(self.point4, self.point1), - ] - - # 该矩形的边长 - def side_lengths(self): - return [line.length() for line in self.sides()] - - # 该矩形是否包含点`point` - def contains(self, point): - point = point_maker(point) - lines = self.sides() - return isclose( - sum([line.point_distance(point) for line in lines]), - sum(self.side_lengths()), - ) - - # 通过关键字`in`判断点是否在矩形内 + # 通过关键字`in`判断点是否在线段上 def __contains__(self, point): - return self.contains(point) + return self.has_on(point) - # 该矩形的面积 - def area(self): - return self.side_lengths()[0] * self.side_lengths()[1] + # 重载`repr`方法,返回Line2D的字符串表示 + def __repr__(self) -> str: + return f"Line2D(expression is {self.expression})" - # 该矩形的周长 - def perimeter(self): - return sum(self.side_lengths()) - - # 该矩形的中心点 - @property - def center(self): - return Point2D( - (self.point1.x + self.point3.x) / 2, (self.point1.y + self.point2.y) / 2 - ) - - # 该矩形的四个顶点 - @property - def vertices(self): - return [self.point1, self.point2, self.point3, self.point4] - - # 该矩形的外接圆 - @property - def circumcircle(self): - pass - - -class Polygon(Shape2D): - def __init__(self, points, **kwargs): - super(Polygon, self).__init__(**kwargs) - self._points = [point_maker(point) for point in points] - self._lines = [Line(self.points[i], self.points[(i + 1) % len(self.points)]) for i in range(len(self.points))] - self._sides = [line.length() for line in self.lines] - self._perimeter = sum(self.sides) - self._center = Point2D(sum([point.x for point in self.points]) / len(self.points), sum([point.y for point in self.points]) / len(self.points)) - - @property - def points(self): - return self._points - - @property - def lines(self): - return self._lines - - @property - def sides(self): - return self._sides - - @property - def perimeter(self): - return self._perimeter - - @property - def center(self): - return self._center - - """ \ No newline at end of file + # 计算点point到此直线的距离 + def distance(self, point: [Tuple[float, float], Point2D]) -> float: + point = point_maker(point) + return squared_distance(self, point) ** 0.5