Spaces:
Running
Running
| """ | |
| 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 | |
| def xmin(self) -> NumberType: | |
| return self.__xmin | |
| def xmax(self) -> NumberType: | |
| return self.__xmax | |
| def ymin(self) -> NumberType: | |
| return self.__ymin | |
| def ymax(self) -> NumberType: | |
| return self.__ymax | |
| def __repr__(self) -> str: | |
| return f'BB: x({self.xmin},{self.xmax}), y({self.ymin},{self.ymax})' | |