feat(tdcgal/cgal.cpp-tdcgal/plane/objects.py): class Point2D Segment2D Ray2D etc

BREAKING CHANGE:
This commit is contained in:
songsenand 2024-02-18 21:26:02 +08:00
parent 33c1958581
commit bbaf3423d7
1 changed files with 125 additions and 235 deletions

View File

@ -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
"""
# 计算点point到此直线的距离
def distance(self, point: [Tuple[float, float], Point2D]) -> float:
point = point_maker(point)
return squared_distance(self, point) ** 0.5