Source code for torx.geometry.ordered_polygon_2d_m

"""Implementation of a 2D ordered closed polygon class."""
import numpy as np
from torx.autodoc_decorators_m import autodoc_class
from scipy.interpolate import splprep, splev
from torx.geometry.polygon_2d_m import Polygon2D
from torx.geometry.line_geometry_m import order_points

[docs] @autodoc_class class OrderedPolygon2D(Polygon2D): """2D polygon which ensures that points are ordered counterclockwise."""
[docs] def __init__(self, x_points: np.ndarray, y_points: np.ndarray, start_idx: int=0, k: int=20, n_interp: int=0, smooth: float=None): """ Initialize the polygon with the vertices given. The points are ordered counterclockwise using a nearest-neighbor algorithm starting from the point with index start_idx and looking for up to k nearest neighbors. If n_interp > 0, the polygon points are interpolated to n_interp points using a periodic spline representation with optional smoothing parameter 'smooth'. Note: smoothing can be useful but high values may lead to very distorted polygons. """ super().__init__(x_points, y_points) # Order the polygon points using a nearest neighbor algorithm self.order(start_idx, k) # Optional: interpolate if n_interp > 0 if n_interp > 0: x_interp, y_interp = self.interpolate(n_interp, smooth) self.x_points = x_interp self.y_points = y_interp # Make sure the polygon points are ordered counterclockwise self._ensure_ccw()
[docs] def order(self, start_idx: int=0, k: int=20): """Order the polygon points using a nearest-neighbor algorithm.""" self.x_points, self.y_points = order_points(self.x_points, self.y_points, start_idx, k) self.ordered = True
def _ensure_ccw(self): """Ensure that the polygon points are ordered counterclockwise.""" if not hasattr(self, 'ccw') or not self.ccw: if not self.ordered: order(self) if self.signed_area() < 0.0: # Reverse the order of the points but keep the first point fixed self.x_points = np.concatenate(([self.x_points[0]], self.x_points[:0:-1])) self.y_points = np.concatenate(([self.y_points[0]], self.y_points[:0:-1])) self.ccw = True
[docs] def interpolate(self, n_interp: int=100, smooth: float=None): """ Interpolate the polygon points using a periodic spline representation. Returns arrays of n_interp interpolated x and y points. """ tck, u = splprep([self.x_points, self.y_points], s=smooth, per=True) self.tck = tck # Check if polygon is closed if not np.isclose(self.x_points[0], self.x_points[-1]) or \ not np.isclose(self.y_points[0], self.y_points[-1]): u_new = np.linspace(0, 1, n_interp, endpoint=False) else: u_new = np.linspace(0, 1, n_interp, endpoint=True) interpolated_x, interpolated_y = splev(u_new, tck) return np.asarray(interpolated_x), np.asarray(interpolated_y)
@property def arclength(self): """Return the cumulative arclength of the polygon.""" self.repeat_first_point() if not hasattr(self, '_arclength'): ds = np.sqrt(np.diff(self.x_points)**2 + np.diff(self.y_points)**2) self._arclength = np.concatenate([[0], np.cumsum(ds)]) return self._arclength @property def centroid(self): """ Return the centroid (center of mass) of the polygon. Shape: (2,) Uses the polygon centroid formula https://en.wikipedia.org/wiki/Centroid#Of_a_polygon """ if not hasattr(self, '_centroid'): x = self.x_points xp1 = np.roll(x, -1) y = self.y_points yp1 = np.roll(y, -1) n = len(x) cx = np.sum((x + xp1) * (x * yp1 - xp1 * y)) / (6 * self.signed_area()) cy = np.sum((yp1 + y) * (x * yp1 - xp1 * y)) / (6 * self.signed_area()) self._centroid = np.array([cx, cy]) return self._centroid
[docs] @classmethod def from_2d_array(cls, data_slice: np.ndarray, start_idx: int=0, k: int=20, n_interp: int=0, smooth: float=None): """Create an instance from a single 2D array slice (npoints, xy).""" return Polygon2D.from_2d_array(data_slice).to_ordered(start_idx, k, n_interp, smooth)