|
|
"""Polygon utility functions.""" |
|
|
|
|
|
from typing import List, Tuple |
|
|
import numpy as np |
|
|
import cv2 |
|
|
|
|
|
|
|
|
def simplify_polygon( |
|
|
polygon: List[Tuple[float, float]], |
|
|
epsilon_percent: float = 1.0 |
|
|
) -> List[Tuple[float, float]]: |
|
|
"""Simplify polygon using Douglas-Peucker algorithm. |
|
|
|
|
|
Args: |
|
|
polygon: List of (x, y) coordinates |
|
|
epsilon_percent: Epsilon as percentage of perimeter |
|
|
|
|
|
Returns: |
|
|
Simplified polygon |
|
|
""" |
|
|
if len(polygon) < 3: |
|
|
return polygon |
|
|
|
|
|
|
|
|
points = np.array(polygon, dtype=np.float32) |
|
|
|
|
|
|
|
|
perimeter = cv2.arcLength(points, True) |
|
|
epsilon = epsilon_percent * perimeter / 100 |
|
|
|
|
|
|
|
|
simplified = cv2.approxPolyDP(points, epsilon, True) |
|
|
|
|
|
|
|
|
return [(float(p[0][0]), float(p[0][1])) for p in simplified] |
|
|
|
|
|
|
|
|
def polygon_area(polygon: List[Tuple[float, float]]) -> float: |
|
|
"""Calculate area of polygon using shoelace formula. |
|
|
|
|
|
Args: |
|
|
polygon: List of (x, y) coordinates |
|
|
|
|
|
Returns: |
|
|
Area of polygon |
|
|
""" |
|
|
if len(polygon) < 3: |
|
|
return 0.0 |
|
|
|
|
|
|
|
|
n = len(polygon) |
|
|
area = 0.0 |
|
|
|
|
|
for i in range(n): |
|
|
j = (i + 1) % n |
|
|
area += polygon[i][0] * polygon[j][1] |
|
|
area -= polygon[j][0] * polygon[i][1] |
|
|
|
|
|
return abs(area) / 2.0 |
|
|
|
|
|
|
|
|
def polygon_centroid(polygon: List[Tuple[float, float]]) -> Tuple[float, float]: |
|
|
"""Calculate centroid of polygon. |
|
|
|
|
|
Args: |
|
|
polygon: List of (x, y) coordinates |
|
|
|
|
|
Returns: |
|
|
Centroid (x, y) |
|
|
""" |
|
|
if not polygon: |
|
|
return (0.0, 0.0) |
|
|
|
|
|
x_sum = sum(p[0] for p in polygon) |
|
|
y_sum = sum(p[1] for p in polygon) |
|
|
|
|
|
return (x_sum / len(polygon), y_sum / len(polygon)) |
|
|
|
|
|
|
|
|
def polygon_bbox(polygon: List[Tuple[float, float]]) -> Tuple[float, float, float, float]: |
|
|
"""Calculate bounding box of polygon. |
|
|
|
|
|
Args: |
|
|
polygon: List of (x, y) coordinates |
|
|
|
|
|
Returns: |
|
|
Bounding box (x1, y1, x2, y2) |
|
|
""" |
|
|
if not polygon: |
|
|
return (0.0, 0.0, 0.0, 0.0) |
|
|
|
|
|
xs = [p[0] for p in polygon] |
|
|
ys = [p[1] for p in polygon] |
|
|
|
|
|
return (min(xs), min(ys), max(xs), max(ys)) |
|
|
|
|
|
|
|
|
def validate_polygon(polygon: List[Tuple[float, float]], min_points: int = 3) -> bool: |
|
|
"""Validate polygon has minimum requirements. |
|
|
|
|
|
Args: |
|
|
polygon: List of (x, y) coordinates |
|
|
min_points: Minimum number of points |
|
|
|
|
|
Returns: |
|
|
True if valid |
|
|
""" |
|
|
return len(polygon) >= min_points and polygon_area(polygon) > 0 |