"""Define one dimensional geometric entities."""
__author__ = "Daniel Ching, Doga Gursoy"
__copyright__ = "Copyright (c) 2016, UChicago Argonne, LLC."
__docformat__ = 'restructuredtext en'
__all__ = [
'Line',
'Segment',
]
import logging
from math import sqrt
import numpy as np
from xdesign.geometry.entity import *
from xdesign.geometry.point import *
logger = logging.getLogger(__name__)
class LinearEntity(Entity):
"""Define a base class for linear entities.
e.g. :class:`.Line`, :class:`.Segment`, and :class:`.Ray`.
The constructor takes two unique :class:`.Point`.
Attributes
----------
p1 : Point
p2 : Point
"""
def __init__(self, p1, p2):
if not isinstance(p1, Point) or not isinstance(p2, Point):
raise TypeError("p1 and p2 must be Points")
if p1 == p2:
raise ValueError('Requires two unique Points.')
if p1.dim != p2.dim:
raise ValueError('Two Points must have same dimensionality.')
self.p1 = p1
self.p2 = p2
self._dim = p1.dim
def __repr__(self):
return "{}({}, {})".format(
type(self).__name__, repr(self.p1), repr(self.p2)
)
@property
def vertical(self):
"""Return True if line is vertical."""
return self.p1.x == self.p2.x
@property
def horizontal(self):
"""Return True if line is horizontal."""
return self.p1.y == self.p2.y
@property
def slope(self):
"""Return the slope of the line."""
if self.vertical:
return np.inf
else:
return ((self.p2.y - self.p1.y) / (self.p2.x - self.p1.x))
@property
def points(self):
"""Return the 2-tuple of points defining this linear entity."""
return (self.p1, self.p2)
@property
def length(self):
"""Return the length of the segment between p1 and p2."""
return self.p1.distance(self.p2)
@property
def tangent(self):
"""Return the unit tangent vector."""
dx = (self.p2._x - self.p1._x) / self.length
return Point(dx)
@property
def normal(self):
"""Return the unit normal vector."""
dx = (self.p2._x - self.p1._x) / self.length
R = np.array([[0, 1], [-1, 0]])
n = np.dot(R, dx)
return Point(n)
@property
def numpy(self):
"""Return row-size numpy array of p1 and p2."""
return np.stack((self.p1._x, self.p2._x), axis=0)
@property
def list(self):
"""Return an list of coordinates where p1 is the first D coordinates
and p2 is the next D coordinates."""
return np.concatenate((self.p1._x, self.p2._x), axis=0)
def translate(self, vector):
"""Translate the :class:`.LinearEntity` by the given vector."""
self.p1.translate(vector)
self.p2.translate(vector)
def rotate(self, theta, point=None, axis=None):
"""Rotate the :class:`.LinearEntity` by theta radians around an axis
defined by an axis and a point."""
self.p1.rotate(theta, point, axis)
self.p2.rotate(theta, point, axis)
[docs]class Line(LinearEntity):
"""Line in 2D cartesian space.
The constructor takes two unique :class:`.Point`.
Attributes
----------
p1 : Point
p2 : Point
"""
def __init__(self, p1, p2):
super(Line, self).__init__(p1, p2)
def __str__(self):
"""Return line equation."""
if self.vertical:
return "x = %s" % self.p1.x
elif self.dim == 2:
return "y = %sx + %s" % (self.slope, self.yintercept)
else:
A, B = self.standard
return "%sx " % '+ '.join([str(n) for n in A]) + "= " + str(B)
def __eq__(self, line):
return (self.slope, self.yintercept) == (line.slope, line.yintercept)
[docs] def intercept(self, n):
"""Calculates the intercept for the nth dimension."""
if n > self._dim:
return 0
else:
A, B = self.standard
if A[n] == 0:
return np.inf
else:
return B / A[n]
@property
def xintercept(self):
"""Return the x-intercept."""
if self.horizontal:
return np.inf
else:
return self.p1.x - 1 / self.slope * self.p1.y
@property
def yintercept(self):
"""Return the y-intercept."""
if self.vertical:
return np.inf
else:
return self.p1.y - self.slope * self.p1.x
@property
def standard(self):
"""Returns coeffients for the first N-1 standard equation coefficients.
The Nth is returned separately."""
A = np.stack([self.p1._x, self.p2._x], axis=0)
return calc_standard(A)
[docs] def distance(self, other):
"""Returns the closest distance between entities."""
# REF: http://geomalgorithms.com/a02-_lines.html
if not isinstance(other, Point):
raise NotImplementedError("Line to point distance only.")
d = np.cross(self.tangent._x, other._x - self.p1._x)
if self.dim > 2:
return sqrt(d.dot(d))
else:
return abs(d)
class Ray(Line):
"""Ray in 2-D cartesian space.
It is defined by two distinct points.
Attributes
----------
p1 : Point (source)
p2 : Point (point direction)
"""
def __init__(self, p1, p2):
super(Ray, self).__init__(p1, p2)
@property
def source(self):
"""The point from which the ray emanates."""
return self.p1
@property
def direction(self):
"""The direction in which the ray emanates."""
return self.p2 - self.p1
def distance(self, other):
# REF: http://geomalgorithms.com/a02-_lines.html
v = self.p2._x - self.p1._x
w = other._x - self.p1._x
c1 = np.dot(w, v)
if c1 <= 0:
return self.p1.distance(other)
else:
return super(Ray, self).distance(other)
[docs]class Segment(Line):
"""Defines a finite line segment from two unique points."""
def __init__(self, p1, p2):
super(Segment, self).__init__(p1, p2)
@property
def midpoint(self):
"""Return the midpoint of the line segment."""
return Point.midpoint(self.p1, self.p2)
[docs] def distance(self, other):
"""Return the distance to the other."""
# REF: http://geomalgorithms.com/a02-_lines.html
v = self.p2._x - self.p1._x
w = other._x - self.p1._x
c1 = np.dot(w, v)
c2 = np.dot(v, v)
if c1 <= 0:
return self.p1.distance(other)
elif c2 <= c1:
return self.p2.distance(other)
else:
return super(Segment, self).distance(other)