rawanessam's picture
Upload 39 files
26f7fa0 verified
"""
MLSTRUCT-FP - DB - LINE 2D
Line definition class.
"""
__all__ = [
'BoundingBox',
'GeomLine2D',
'GeomPoint2D'
]
from MLStructFP.utils._mathlib import dist2
from MLStructFP._types import Dict, Any, List, Union, Tuple, NumberType, NumberInstance
import math
import matplotlib.pyplot as plt
MIN_TOL: float = 1e-12
class GeomLine2D(object):
"""
Geometric 2d segment between 2 points.
"""
_def_points: Dict[str, 'GeomPoint2D']
m: NumberType
n: NumberType
theta: NumberType
def __init__(self) -> None:
"""
Constructor.
"""
self.m = 0.0
self.theta = 0.0
self.n = 0.0
self._def_points = dict(
p1=GeomPoint2D(),
p2=GeomPoint2D()
)
def clone(self) -> 'GeomLine2D':
"""
Returns a cloned object.
:return: New line
"""
line = GeomLine2D()
line.m = self.m
line.theta = self.theta
line.n = self.n
line._def_points['p1'] = self._def_points['p1'].clone()
line._def_points['p2'] = self._def_points['p2'].clone()
return line
def __str__(self) -> str:
return f'y = {self.m}*x + {self.n}. Theta: {self.theta}'
def __repr__(self) -> str:
return str(self)
def from_2_points(self, p1: 'GeomPoint2D', p2: 'GeomPoint2D') -> 'GeomLine2D':
"""
Set line from 2 points.
:param p1: Point 1
:param p2: Point 2
:return: Self
"""
self._def_points['p1'] = p1.clone()
self._def_points['p2'] = p2.clone()
m_a = p2.y - p1.y
m_b = p2.x - p1.x + MIN_TOL
self.m = m_a / m_b
self.n = (-self.m * p1.x) + p2.y
self.theta = math.atan2(m_a, m_b)
return self
def eval(self, x: NumberType) -> NumberType:
"""
Eval line at given value.
:param x: Evaluation point
:return: Evaluated Ƒ(x)
"""
return (self.m * x) + self.n
def f(self, x: NumberType) -> 'GeomPoint2D':
"""
Returns new point from (x,Ƒ(x)).
:param x: Evaluation point
:return: New point
"""
return GeomPoint2D(x, self.eval(x))
def _check_def_none(self) -> None:
"""
Checks defined points are none.
"""
if self._def_points['p1'] is None or self._def_points['p2'] is None:
self._def_points['p1'] = self.f(0)
self._def_points['p2'] = self.f(1)
def point_left(self, p: 'GeomPoint2D') -> bool:
"""
Check point is at left of the segment.
:param p: Point
:return: True if the point is located at the left of the segment
"""
self._check_def_none()
p1: 'GeomPoint2D' = self._def_points['p1']
p2: 'GeomPoint2D' = self._def_points['p2']
return (((p2.x - p1.x) * (p.y - p1.y)) - ((p2.y - p1.y) * (p.x - p1.x))) > 0
def point_on(self, p: 'GeomPoint2D') -> bool:
"""
Check point on segment.
:param p: Point
:return: True if point is on the segment
"""
self._check_def_none()
p1: 'GeomPoint2D' = self._def_points['p1']
p2: 'GeomPoint2D' = self._def_points['p2']
return math.fabs((((p2.x - p1.x) * (p.y - p1.y)) - ((p2.y - p1.y) * (p.x - p1.x)))) < MIN_TOL
def ortho_distance_point_list(self, point_list: List['GeomPoint2D']) -> List[NumberType]:
"""
Calculate the orthographic distance from a point.
:param point_list: Point list
:return: Distance list
"""
dist = []
for i in range(len(point_list)):
dist.append(self.ortho_distance_point(point_list[i]))
return dist
def ortho_distance_point(self, p: 'GeomPoint2D') -> NumberType:
"""
Returns the orthographic distance from a point.
:param p: Point
:return: Distance
"""
a = -self.m
b = 1
c = -self.n
return math.fabs((a * p.x) + (b * p.y) + c) / dist2(a, b)
def ortho_distance_line(self, line: 'GeomLine2D', force: bool = False) -> NumberType:
"""
Calculate the orthographic distance from another line.
:param line: Line
:param force: If true, vertical segments will be evaluated on x-axis
:return: Distance
"""
if math.fabs(math.fabs(self.m) - math.fabs(line.m)) > 1e-4:
if not force:
return math.inf
else:
return math.fabs(self.eval(0) - line.eval(0))
return math.fabs(self.n - line.n) / dist2(1, self.m)
class GeomPoint2D(object):
"""
2D coordinate.
"""
_data: Dict[str, Any]
x: NumberType
y: NumberType
def __init__(self, x: NumberType = 0, y: NumberType = 0) -> None:
"""
Constructor.
:param x: X coordinate
:param y: Y coordinate
"""
assert isinstance(x, NumberInstance)
assert isinstance(y, NumberInstance)
self.x = float(x)
self.y = float(y)
self._data = {} # Point inner data
def __eq__(self, other: 'GeomPoint2D') -> bool:
return math.fabs(self.x - other.x) <= MIN_TOL and math.fabs(self.y - other.y) <= MIN_TOL
def __ne__(self, other: 'GeomPoint2D') -> bool:
"""
Returns true if points are not equal.
:param other: Point
:return: bool
"""
return math.fabs(self.x - other.x) > MIN_TOL or math.fabs(self.y - other.y) > MIN_TOL
def __mul__(self, other: 'GeomPoint2D') -> 'GeomPoint2D':
"""
Multiply point with another.
:param other: Point
:return: Self
"""
self.x *= other.x
self.y *= other.y
return self
def __add__(self, other: 'GeomPoint2D') -> 'GeomPoint2D':
"""
Add point with another.
:param other: Point
:return: Self
"""
self.x += other.x
self.y += other.y
return self
def __sub__(self, other: 'GeomPoint2D') -> 'GeomPoint2D':
"""
Subtract point with another.
:param other: Point
:return: Self
"""
self.x -= other.x
self.y -= other.y
return self
def __str__(self) -> str:
return f'({self.x},{self.y})'
def __repr__(self) -> str:
return str(self)
def list(self) -> List[float]:
"""
Returns the point as a list.
:return: Point list [x,y]
"""
return [self.x, self.y]
def tuple(self) -> Tuple[float, float]:
"""
Returns the tuple of the point.
:return: Tuple (x, y)
"""
return self.x, self.y
def rotate(self, center: 'GeomPoint2D', angle: float = 0) -> 'GeomPoint2D':
"""
Rotate the point around a given center.
:param center: Rotation center
:param angle: Rotation angle, angles in degrees
:return: Self
"""
if angle == 0:
return self
s = math.sin(angle * math.pi / 180)
c = math.cos(angle * math.pi / 180)
# Translate
self.x -= center.x
self.y -= center.y
# Rotate
new_x = self.x * c - self.y * s
new_y = self.x * s + self.y * c
# Update
self.x = new_x + center.x
self.y = new_y + center.y
return self
def scale(self, s: float) -> 'GeomPoint2D':
"""
Scale point with number.
:param s: Scale factor
:return: Self
"""
self.x *= s
self.y *= s
return self
def dist(self, other: 'GeomPoint2D') -> float:
"""
Returns the distance between two points.
:param other: Point
:return: Distance
"""
return dist2(self.x, self.y, other.x, other.y)
def angle(self, other: Union['GeomPoint2D', List['GeomPoint2D']]) -> Union[float, List[float]]:
"""
Returns the angle between two points.
:param other: Point
:return: Angle
"""
if isinstance(other, list):
dist = []
for i in range(len(other)):
dist.append(self.angle(other[i]))
return dist
if self.x == other.x and self.y == other.y:
return 0.0
return math.atan2(other.y - self.y, other.x - self.x)
def set_property(self, key: str, data: Any) -> None:
"""
Set point property.
:param key: Key
:param data: Data
"""
if data is None:
return
assert isinstance(key, str)
self._data[key] = data
def get_property(self, key: str, default: Any = None) -> Any:
"""
Get point property.
:param key: Key
:param default: Data
:return: Value
"""
if not self.has_property(key):
return default
return self._data[key]
def has_property(self, key: str) -> bool:
"""
Returns true if point has data.
:param key: Key
:return: True if key exists
"""
return key in self._data.keys()
def clone(self) -> 'GeomPoint2D':
"""
Clone point.
:return: New point
"""
p = GeomPoint2D(self.x, self.y)
for k in self._data.keys():
p.set_property(k, self.get_property(k))
return p
def equals(self, other: 'GeomPoint2D') -> bool:
"""
Points is equal to another.
:param other: Point
:return: True if points is equal
"""
return math.fabs(self.x - other.x) < MIN_TOL and math.fabs(self.y - other.y) < MIN_TOL
def set_zero(self) -> None:
"""
Set point as zero.
"""
self.x = 0
self.y = 0
def plot(self, ax: 'plt.Axes', color='#000000', marker_size: int = 10, style: str = '.') -> None:
"""
Plot point.
:param ax: Matplotlib axes
:param color: Marker color
:param marker_size: Marker size
:param style: Marker style
"""
assert isinstance(ax, plt.Axes)
assert isinstance(color, str)
assert isinstance(marker_size, int)
assert isinstance(style, str)
ax.plot(self.x, self.y, style, markersize=marker_size, color=color)
class BoundingBox(object):
"""
Represents a bounding box from (xmin, xmax) to (ymin, ymax).
"""
__xmin: NumberType
__xmax: NumberType
__ymin: NumberType
__ymax: NumberType
def __init__(self, xmin: NumberType, xmax: NumberType, ymin: NumberType, ymax: NumberType) -> None:
"""
Constructor.
"""
self.__xmin = xmin
self.__xmax = xmax
self.__ymin = ymin
self.__ymax = ymax
@property
def xmin(self) -> NumberType:
return self.__xmin
@property
def xmax(self) -> NumberType:
return self.__xmax
@property
def ymin(self) -> NumberType:
return self.__ymin
@property
def ymax(self) -> NumberType:
return self.__ymax
def __repr__(self) -> str:
return f'BB: x({self.xmin},{self.xmax}), y({self.ymin},{self.ymax})'