from math import isclose from sympy import symbols, solve from loguru import logger from ..cgal_bindings.plane import Point_2, Segment_2, Line_2, squared_distance def point_maker(pre_point): if isinstance(pre_point, Point2D): return pre_point elif isinstance(pre_point, tuple): return Point2D(pre_point[0], pre_point[1]) else: print(type(pre_point)) raise ValueError("Invalid input for Point2D") class Shape2D(object): def __init__(self, **kwargs): self.color = kwargs.get("color", "white") 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): self.point == point_maker(other) def __repr__(self): return f"Point2D(x: {self.x}, y: {self.y})" class Segment2D(Shape2D, Segment_2): def __init__(self, point1, point2, **kwargs): point1 = point_maker(point1) point2 = point_maker(point2) super(Shape2D, self).__init__(**kwargs) super(Segment_2, self).__init__(point1, point2) # 计算线段的长度 def length(self): return self.squared_length() ** 0.5 # 重载==运算符,判断两个Segment2D实例是否为同一条线段 def __eq__(self, other) -> bool: return self == other # 重载`in`运算符,判断点是否在线段上 def __contains__(self, point) -> bool: return self.has_on(point) # 重载`repr`方法,返回Segment2D的字符串表示 def __repr__(self) -> str: return f"Segment2D({self.point1}, {self.point2})" # 线段的起点`source` @property def source(self): return self._source() # 线段的终点`target` @property def target(self): return self._target() # 直线类 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 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. Returns: _type_: boolean """""" point = point_maker(point) 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 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) # 计算点`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 # 该线段的斜率 @property def slope(self): return self._slope # 该线段所在直线的函数方程 @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 # 该线段的函数式 @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`判断点是否在矩形内 def __contains__(self, point): return self.contains(point) # 该矩形的面积 def area(self): return self.side_lengths()[0] * self.side_lengths()[1] # 该矩形的周长 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 """