def __init__(self, point: array_like, radius: float): if radius <= 0: raise ValueError("The radius must be positive.") self.point = Point(point) self.radius = radius self.dimension = self.point.dimension
def __init__(self, point_a: array_like, point_b: array_like, point_c: array_like): self.point_a = Point(point_a) self.point_b = Point(point_b) self.point_c = Point(point_c) if not (self.point_a.dimension == self.point_b.dimension == self.point_c.dimension): raise ValueError("The points must have the same dimension.") if Points([self.point_a, self.point_b, self.point_c]).are_collinear(): raise ValueError("The points must not be collinear.") self.dimension = self.point_a.dimension
def project_point(self, point: array_like) -> Point: """ Project a point onto the plane. Parameters ---------- point : array_like Input point. Returns ------- Point Projection of the point onto the plane. Examples -------- >>> from skspatial.objects import Plane >>> plane = Plane(point=[0, 0, 0], normal=[0, 0, 2]) >>> plane.project_point([10, 2, 5]) Point([10., 2., 0.]) >>> plane.project_point([5, 9, -3]) Point([5., 9., 0.]) """ # Vector from the point in space to the point on the plane. vector_to_plane = Vector.from_points(point, self.point) # Perpendicular vector from the point in space to the plane. vector_projected = self.normal.project_vector(vector_to_plane) return Point(point) + vector_projected
def __init__(self, point: array_like, vector: array_like, **kwargs): self.point = Point(point) self.vector = Vector(vector) if self.point.dimension != self.vector.dimension: raise ValueError( "The point and vector must have the same dimension.") if self.vector.is_zero(**kwargs): raise ValueError("The vector must not be the zero vector.") self.dimension = self.point.dimension
def __init__(self, point: array_like, vector: array_like, error = None): self.point = Point(point) self.vector = Vector(vector) self.error = error if self.point.dimension != self.vector.dimension: raise ValueError("The point and vector must have the same dimension.") if self.vector.is_zero(rel_tol=0, abs_tol=0): raise ValueError("The vector must not be the zero vector.") self.dimension = self.point.dimension
def from_vectors(cls, point: array_like, vector_a: array_like, vector_b: array_like, **kwargs) -> Plane: """ Instantiate a plane from a point and two vectors. The two vectors span the plane. Parameters ---------- point : array_like Point on the plane. vector_a, vector_b : array_like Input vectors. kwargs : dict, optional Additional keywords passed to :meth:`Vector.is_parallel`. Returns ------- Plane Plane containing input point and spanned by the two input vectors. Raises ------ ValueError If the vectors are parallel. Examples -------- >>> from skspatial.objects import Plane >>> Plane.from_vectors([0, 0], [1, 0], [0, 1]) Plane(point=Point([0, 0, 0]), normal=Vector([0, 0, 1])) >>> Plane.from_vectors([0, 0], [1, 0], [2, 0]) Traceback (most recent call last): ... ValueError: The vectors must not be parallel. """ vector_a = Vector(vector_a) if vector_a.is_parallel(vector_b, **kwargs): raise ValueError("The vectors must not be parallel.") # The cross product returns a 3D vector. vector_normal = vector_a.cross(vector_b) # Convert the point to 3D so that it matches the vector dimension. point = Point(point).set_dimension(3) return cls(point, vector_normal)
def centroid(self) -> Point: """ Return the centroid of the points. Returns ------- Point Centroid of the points. Examples -------- >>> from skspatial.objects import Points >>> Points([[1, 2, 3], [2, 2, 3]]).centroid() Point([1.5, 2. , 3. ]) """ return Point(self.mean(axis=0))
def __init__(self, point: array_like, vector: array_like, radius: float): self.point = Point(point) self.vector = Vector(vector) if self.point.dimension != 3: raise ValueError("The point must be 3D.") if self.vector.dimension != 3: raise ValueError("The vector must be 3D.") if self.vector.is_zero(): raise ValueError("The vector must not be the zero vector.") if not radius > 0: raise ValueError("The radius must be positive.") self.radius = radius self.dimension = self.point.dimension
def intersect_plane(self, other: Plane) -> Line: """ Intersect the plane with another. The planes must not be parallel. Parameters ---------- other : Plane Other plane. Returns ------- Line The line of intersection. Raises ------ ValueError If the planes are parallel. References ---------- http://tbirdal.blogspot.com/2016/10/a-better-approach-to-plane-intersection.html Examples -------- >>> from skspatial.objects import Plane >>> plane_a = Plane([0, 0, 0], [0, 0, 1]) >>> plane_b = Plane([0, 0, 0], [1, 0, 0]) >>> plane_a.intersect_plane(plane_b) Line(point=Point([0., 0., 0.]), direction=Vector([0, 1, 0])) >>> plane_b = Plane([5, 16, -94], [1, 0, 0]) >>> plane_a.intersect_plane(plane_b) Line(point=Point([5., 0., 0.]), direction=Vector([0, 1, 0])) >>> plane_b = Plane([0, 0, 1], [1, 0, 1]) >>> plane_a.intersect_plane(plane_b) Line(point=Point([1., 0., 0.]), direction=Vector([0, 1, 0])) >>> plane_b = Plane([0, 0, 5], [0, 0, -8]) >>> plane_a.intersect_plane(plane_b) Traceback (most recent call last): ... ValueError: The planes must not be parallel. """ if self.normal.is_parallel(other.normal, rel_tol=0, abs_tol=0): raise ValueError("The planes must not be parallel.") array_normals_stacked = np.vstack((self.normal, other.normal)) # Construct a matrix for a linear system. array_00 = 2 * np.eye(3) array_01 = array_normals_stacked.T array_10 = array_normals_stacked array_11 = np.zeros((2, 2)) matrix = np.block([[array_00, array_01], [array_10, array_11]]) dot_a = np.dot(self.point, self.normal) dot_b = np.dot(other.point, other.normal) array_y = np.array([0, 0, 0, dot_a, dot_b]) # Solve the linear system. solution = np.linalg.solve(matrix, array_y) point_line = Point(solution[:3]) direction_line = self.normal.cross(other.normal) return Line(point_line, direction_line)
def intersect_line(self, line: Line) -> Tuple[Point, Point]: """ Intersect the circle with a line. A line intersects a circle at two points. Parameters ---------- line : Line Input line. Returns ------- point_a, point_b : Point The two points of intersection. Raises ------ ValueError If the line does not intersect the circle. References ---------- http://mathworld.wolfram.com/Circle-LineIntersection.html Examples -------- >>> from skspatial.objects import Circle, Line >>> circle = Circle([0, 0], 1) >>> circle.intersect_line(Line(point=[0, 0], direction=[1, 0])) (Point([-1., 0.]), Point([1., 0.])) >>> point_a, point_b = circle.intersect_line(Line(point=[0, 0], direction=[1, 1])) >>> point_a.round(3) Point([-0.707, -0.707]) >>> point_b.round(3) Point([0.707, 0.707]) >>> circle.intersect_line(Line(point=[1, 2], direction=[1, 1])) (Point([-1., 0.]), Point([0., 1.])) If the line is tangent to the circle, the two intersection points are the same. >>> circle.intersect_line(Line(point=[1, 0], direction=[0, 1])) (Point([1., 0.]), Point([1., 0.])) The circle does not have to be centered on the origin. >>> point_a, point_b = Circle([2, 3], 5).intersect_line(Line([1, 1], [2, 3])) >>> point_a.round(3) Point([-0.538, -1.308]) >>> point_b.round(3) Point([5., 7.]) >>> circle.intersect_line(Line(point=[5, 0], direction=[1, 1])) Traceback (most recent call last): ... ValueError: The line does not intersect the circle. """ # Two points on the line. # Copy the line point to avoid changing the line itself. point_1 = np.copy(line.point) point_2 = point_1 + line.direction.unit() # Translate the points on the line to mimic the circle being centered on the origin. point_1 -= self.point point_2 -= self.point x_1, y_1 = point_1 x_2, y_2 = point_2 d_x = x_2 - x_1 d_y = y_2 - y_1 # Pre-compute variables common to x and y equations. d_r_squared = d_x**2 + d_y**2 determinant = x_1 * y_2 - x_2 * y_1 discriminant = self.radius**2 * d_r_squared - determinant**2 if discriminant < 0: raise ValueError("The line does not intersect the circle.") root = math.sqrt(discriminant) pm = np.array([-1, 1]) # Array to compute plus/minus. sign = -1 if d_y < 0 else 1 coords_x = (determinant * d_y + pm * sign * d_x * root) / d_r_squared coords_y = (-determinant * d_x + pm * abs(d_y) * root) / d_r_squared point_a = Point([coords_x[0], coords_y[0]]) point_b = Point([coords_x[1], coords_y[1]]) # Translate the intersection points back from origin circle to real circle. point_a += self.point point_b += self.point return point_a, point_b
class _BaseSphere: """Private parent class for Circle and Sphere.""" def __init__(self, point: array_like, radius: float): if radius <= 0: raise ValueError("The radius must be positive.") self.point = Point(point) self.radius = radius self.dimension = self.point.dimension def __repr__(self) -> str: name_class = type(self).__name__ repr_point = np.array_repr(self.point) return f"{name_class}(point={repr_point}, radius={self.radius})" def distance_point(self, point: array_like) -> np.float64: """Return the distance from a point to the circle/sphere.""" distance_to_center = self.point.distance_point(point) return abs(distance_to_center - self.radius) def contains_point(self, point: array_like, **kwargs: float) -> bool: """Check if the line/plane contains a point.""" return _contains_point(self, point, **kwargs) def project_point(self, point: array_like) -> Point: """ Project a point onto the circle or sphere. Parameters ---------- point : array_like Input point. Returns ------- Point Point projected onto the circle or sphere. Raises ------ ValueError If the input point is the center of the circle or sphere. Examples -------- >>> from skspatial.objects import Circle >>> circle = Circle([0, 0], 1) >>> circle.project_point([1, 1]).round(3) Point([0.707, 0.707]) >>> circle.project_point([-6, 3]).round(3) Point([-0.894, 0.447]) >>> circle.project_point([0, 0]) Traceback (most recent call last): ... ValueError: The point must not be the center of the circle or sphere. >>> from skspatial.objects import Sphere >>> Sphere([0, 0, 0], 2).project_point([1, 2, 3]).round(3) Point([0.535, 1.069, 1.604]) """ if self.point.is_equal(point): raise ValueError( "The point must not be the center of the circle or sphere.") vector_to_point = Vector.from_points(self.point, point) return self.point + self.radius * vector_to_point.unit() def plotter( self, **kwargs ) -> Union[Callable[[Axes], None], Callable[[Axes3D], None]]: return _plotter(self, **kwargs)