diff options
Diffstat (limited to 'lib/utils')
| -rw-r--r-- | lib/utils/clamp_path.py | 23 | ||||
| -rw-r--r-- | lib/utils/geometry.py | 31 | ||||
| -rw-r--r-- | lib/utils/list.py | 4 |
3 files changed, 58 insertions, 0 deletions
diff --git a/lib/utils/clamp_path.py b/lib/utils/clamp_path.py index 0f58f83c..fcb34a4b 100644 --- a/lib/utils/clamp_path.py +++ b/lib/utils/clamp_path.py @@ -1,5 +1,6 @@ from shapely.geometry import LineString, MultiPolygon from shapely.geometry import Point as ShapelyPoint +from shapely.ops import nearest_points from shapely.prepared import prep from .geometry import (Point, ensure_geometry_collection, @@ -14,6 +15,9 @@ def path_to_segments(path): def segments_to_path(segments): """Convert a list of contiguous LineStrings into a list of Points.""" + if not segments: + return [] + coords = [segments[0].coords[0]] for segment in segments: @@ -68,6 +72,22 @@ def find_border(polygon, point): return polygon.exterior +def clamp_fully_external_path(path, polygon): + """Clamp a path that lies entirely outside a polygon.""" + + start = ShapelyPoint(path[0]) + end = ShapelyPoint(path[-1]) + + start_on_outline = nearest_points(start, polygon.exterior)[1].buffer(0.01, resolution=1) + end_on_outline = nearest_points(end, polygon.exterior)[1].buffer(0.01, resolution=1) + + border_pieces = ensure_multi_line_string(polygon.exterior.difference(MultiPolygon((start_on_outline, end_on_outline)))).geoms + border_pieces = fix_starting_point(border_pieces) + shorter = min(border_pieces, key=lambda piece: piece.length) + + return adjust_line_end(shorter, start) + + def clamp_path_to_polygon(path, polygon): """Constrain a path to a Polygon. @@ -143,4 +163,7 @@ def clamp_path_to_polygon(path, polygon): else: was_inside = False + if not result: + return clamp_fully_external_path(path, polygon) + return segments_to_path(result) diff --git a/lib/utils/geometry.py b/lib/utils/geometry.py index 24cf8459..541d9cbc 100644 --- a/lib/utils/geometry.py +++ b/lib/utils/geometry.py @@ -197,6 +197,37 @@ def cut_path(points, length): return [Point(*point) for point in subpath.coords] +def offset_points(pos1, pos2, offset_px, offset_proportional): + """Expand or contract two points about their midpoint. + + This is useful for pull compensation and insetting underlay. + """ + + distance = (pos1 - pos2).length() + + if distance < 0.0001: + # if they're the same point, we don't know which direction + # to offset in, so we have to just return the points + return pos1, pos2 + + # calculate the offset for each side + offset_a = offset_px[0] + (distance * offset_proportional[0]) + offset_b = offset_px[1] + (distance * offset_proportional[1]) + offset_total = offset_a + offset_b + + # don't contract beyond the midpoint, or we'll start expanding + if offset_total < -distance: + scale = -distance / offset_total + offset_a = offset_a * scale + offset_b = offset_b * scale + + # convert offset to float before using because it may be a numpy.float64 + out1 = pos1 + (pos1 - pos2).unit() * float(offset_a) + out2 = pos2 + (pos2 - pos1).unit() * float(offset_b) + + return out1, out2 + + class Point: def __init__(self, x: typing.Union[float, numpy.float64], y: typing.Union[float, numpy.float64]): self.x = float(x) diff --git a/lib/utils/list.py b/lib/utils/list.py index efa3969e..e5838b5a 100644 --- a/lib/utils/list.py +++ b/lib/utils/list.py @@ -21,3 +21,7 @@ def poprandom(sequence, rng=_rng): sequence[index] = last_item return item + + +def is_all_zeroes(sequence): + return all(item == 0 for item in sequence) |
