axi/axi/paths.py

182 lines
5.2 KiB
Python

from math import hypot
from pyhull.convex_hull import ConvexHull
from shapely import geometry
from .spatial import Index
def load_paths(filename):
paths = []
with open(filename) as fp:
for line in fp:
points = filter(None, line.strip().split(';'))
if not points:
continue
path = [tuple(map(float, x.split(','))) for x in points]
paths.append(path)
return paths
def path_length(points):
result = 0
for (x1, y1), (x2, y2) in zip(points, points[1:]):
result += hypot(x2 - x1, y2 - y1)
return result
def paths_length(paths):
return sum([path_length(path) for path in paths], 0)
def simplify_path(points, tolerance):
if len(points) < 2:
return points
line = geometry.LineString(points)
line = line.simplify(tolerance, preserve_topology=False)
return list(line.coords)
def simplify_paths(paths, tolerance):
return [simplify_path(x, tolerance) for x in paths]
def sort_paths(paths, reversable=True):
first = paths[0]
paths.remove(first)
result = [first]
points = []
for path in paths:
x1, y1 = path[0]
x2, y2 = path[-1]
points.append((x1, y1, path, False))
if reversable:
points.append((x2, y2, path, True))
index = Index(points)
while index.size > 0:
x, y, path, reverse = index.nearest(result[-1][-1])
x1, y1 = path[0]
x2, y2 = path[-1]
index.remove((x1, y1, path, False))
if reversable:
index.remove((x2, y2, path, True))
if reverse:
result.append(list(reversed(path)))
else:
result.append(path)
return result
def join_paths(paths, tolerance):
if len(paths) < 2:
return paths
result = [list(paths[0])]
for path in paths[1:]:
x1, y1 = result[-1][-1]
x2, y2 = path[0]
d = hypot(x2 - x1, y2 - y1)
if d <= tolerance:
result[-1].extend(path)
else:
result.append(list(path))
return result
def crop_interpolate(x1, y1, x2, y2, ax, ay, bx, by):
dx = bx - ax
dy = by - ay
t1 = (x1 - ax) / dx if dx else -1
t2 = (y1 - ay) / dy if dy else -1
t3 = (x2 - ax) / dx if dx else -1
t4 = (y2 - ay) / dy if dy else -1
ts = [t1, t2, t3, t4]
ts = [t for t in ts if t >= 0 and t <= 1]
t = min(ts)
x = ax + (bx - ax) * t
y = ay + (by - ay) * t
return (x, y)
def crop_path(path, x1, y1, x2, y2):
e = 1e-9
result = []
buf = []
previous_point = None
previous_inside = False
for x, y in path:
inside = x >= x1 - e and y >= y1 - e and x <= x2 + e and y <= y2 + e
if inside:
if not previous_inside and previous_point:
px, py = previous_point
ix, iy = crop_interpolate(x1, y1, x2, y2, x, y, px, py)
buf.append((ix, iy))
buf.append((x, y))
else:
if previous_inside and previous_point:
px, py = previous_point
ix, iy = crop_interpolate(x1, y1, x2, y2, x, y, px, py)
buf.append((ix, iy))
result.append(buf)
buf = []
previous_point = (x, y)
previous_inside = inside
if buf:
result.append(buf)
return result
def crop_paths(paths, x1, y1, x2, y2):
result = []
for path in paths:
result.extend(crop_path(path, x1, y1, x2, y2))
return result
def convex_hull(points):
hull = ConvexHull(points)
vertices = set(i for v in hull.vertices for i in v)
return [hull.points[i] for i in vertices]
def quadratic_path(x0, y0, x1, y1, x2, y2):
n = int(hypot(x1 - x0, y1 - y0) + hypot(x2 - x1, y2 - y1))
n = max(n, 4)
points = []
m = 1 / float(n - 1)
for i in range(n):
t = i * m
u = 1 - t
a = u * u
b = 2 * u * t
c = t * t
x = a * x0 + b * x1 + c * x2
y = a * y0 + b * y1 + c * y2
points.append((x, y))
return points
def expand_quadratics(path):
result = []
previous = (0, 0)
for point in path:
if len(point) == 2:
result.append(point)
previous = point
elif len(point) == 4:
x0, y0 = previous
x1, y1, x2, y2 = point
result.extend(quadratic_path(x0, y0, x1, y1, x2, y2))
previous = (x2, y2)
else:
raise Exception('invalid point: %r' % point)
return result
def paths_to_shapely(paths):
# TODO: Polygons for closed paths?
return geometry.MultiLineString(paths)
def shapely_to_paths(g):
if isinstance(g, geometry.Point):
return []
elif isinstance(g, geometry.LineString):
return [list(g.coords)]
elif isinstance(g, (geometry.MultiPoint, geometry.MultiLineString, geometry.MultiPolygon, geometry.collection.GeometryCollection)):
paths = []
for x in g:
paths.extend(shapely_to_paths(x))
return paths
elif isinstance(g, geometry.Polygon):
paths = []
paths.append(list(g.exterior.coords))
for interior in g.interiors:
paths.extend(shapely_to_paths(interior))
return paths
else:
raise Exception('unhandled shapely geometry: %s' % type(g))