317 lines
9.4 KiB
Python
317 lines
9.4 KiB
Python
from math import isclose
|
||
|
||
from sympy import symbols, solve
|
||
|
||
from loguru import logger
|
||
|
||
from ..cgal_bindings.plane import Point_2, Segment_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()
|
||
|
||
# 与线段共线的直线
|
||
def supporting_line(self) -> Line:
|
||
return self.supporting_line()
|
||
|
||
"""class Line(Shape2D):
|
||
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
|
||
|
||
""" |