1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
|
import math
from itertools import islice
import numpy as np
import shapely
from shapely.affinity import affine_transform
def _oriented_envelope_min_area(geometry, **kwargs):
"""Compute the oriented envelope (minimum rotated rectangle).
This is a fallback implementation for GEOS < 3.12 to have the correct
minimum area behaviour.
"""
if geometry is None:
return None
if geometry.is_empty:
return shapely.from_wkt("POLYGON EMPTY")
# first compute the convex hull
hull = geometry.convex_hull
try:
coords = hull.exterior.coords
except AttributeError: # may be a Point or a LineString
return hull
# generate the edge vectors between the convex hull's coords
edges = (
(pt2[0] - pt1[0], pt2[1] - pt1[1])
for pt1, pt2 in zip(coords, islice(coords, 1, None))
)
def _transformed_rects():
for dx, dy in edges:
# compute the normalized direction vector of the edge
# vector.
length = math.sqrt(dx**2 + dy**2)
ux, uy = dx / length, dy / length
# compute the normalized perpendicular vector
vx, vy = -uy, ux
# transform hull from the original coordinate system to
# the coordinate system defined by the edge and compute
# the axes-parallel bounding rectangle.
transf_rect = affine_transform(hull, (ux, uy, vx, vy, 0, 0)).envelope
# yield the transformed rectangle and a matrix to
# transform it back to the original coordinate system.
yield (transf_rect, (ux, vx, uy, vy, 0, 0))
# check for the minimum area rectangle and return it
transf_rect, inv_matrix = min(_transformed_rects(), key=lambda r: r[0].area)
return affine_transform(transf_rect, inv_matrix)
_oriented_envelope_min_area_vectorized = np.frompyfunc(
_oriented_envelope_min_area, 1, 1
)
|