kopia lustrzana https://github.com/inkstitch/inkstitch
remove sample_linestring and point_transfer
rodzic
bd8cb0d1ff
commit
8a1f70a6cd
|
@ -1,503 +0,0 @@
|
|||
import math
|
||||
from collections import namedtuple
|
||||
|
||||
from shapely.geometry import LineString, LinearRing, MultiPoint, Point
|
||||
from shapely.ops import nearest_points
|
||||
|
||||
from ..stitches import constants, sample_linestring
|
||||
|
||||
"""This file contains routines which shall project already selected points for stitching to remaining
|
||||
unstitched lines in the neighborhood to create a regular pattern of points."""
|
||||
|
||||
projected_point_tuple = namedtuple(
|
||||
'projected_point_tuple', ['point', 'point_source'])
|
||||
|
||||
|
||||
def calc_transferred_point(bisectorline, child):
|
||||
"""
|
||||
Calculates the nearest intersection point of "bisectorline" with the coordinates of child (child.val).
|
||||
It returns the intersection point and its distance along the coordinates of the child or "None, None" if no
|
||||
intersection was found.
|
||||
"""
|
||||
result = bisectorline.intersection(child.val)
|
||||
if result.is_empty:
|
||||
return None, None
|
||||
desired_point = Point()
|
||||
if result.geom_type == 'Point':
|
||||
desired_point = result
|
||||
elif result.geom_type == 'LineString':
|
||||
desired_point = Point(result.coords[0])
|
||||
else:
|
||||
resultlist = list(result)
|
||||
desired_point = resultlist[0]
|
||||
if len(resultlist) > 1:
|
||||
desired_point = nearest_points(
|
||||
result, Point(bisectorline.coords[0]))[0]
|
||||
|
||||
priority = child.val.project(desired_point)
|
||||
point = desired_point
|
||||
return point, priority
|
||||
|
||||
|
||||
def transfer_points_to_surrounding(tree, node, used_offset, offset_by_half, to_transfer_points, to_transfer_points_origin=[], # noqa: C901
|
||||
overnext_neighbor=False, transfer_forbidden_points=False,
|
||||
transfer_to_parent=True, transfer_to_sibling=True, transfer_to_child=True):
|
||||
"""
|
||||
Takes the current tree item and its rastered points (to_transfer_points) and transfers these points to its parent, siblings and childs
|
||||
To do so it calculates the current normal and determines its intersection with the neighbors which gives the transferred points.
|
||||
Input:
|
||||
-treenode: Tree node whose points stored in "to_transfer_points" shall be transferred to its neighbors.
|
||||
-used_offset: The used offset when the curves where offsetted
|
||||
-offset_by_half: True if the transferred points shall be interlaced with respect to the points in "to_transfer_points"
|
||||
-to_transfer_points: List of points belonging to treenode which shall be transferred - it is assumed that to_transfer_points
|
||||
can be handled as closed ring
|
||||
-to_transfer_points_origin: The origin tag of each point in to_transfer_points
|
||||
-overnext_neighbor: Transfer the points to the overnext neighbor (gives a more stable interlacing)
|
||||
-transfer_forbidden_points: Only allowed for interlacing (offset_by_half): Might be used to transfer points unshifted as
|
||||
forbidden points to the neighbor to avoid a point placing there
|
||||
-transfer_to_parent: If True, points will be transferred to the parent
|
||||
-transfer_to_sibling: If True, points will be transferred to the siblings
|
||||
-transfer_to_child: If True, points will be transferred to the childs
|
||||
Output:
|
||||
-Fills the attribute "transferred_point_priority_deque" of the siblings and parent in the tree datastructure. An item of the deque
|
||||
is setup as follows: ((projected point on line, LineStringSampling.PointSource), priority=distance along line)
|
||||
index of point_origin is the index of the point in the neighboring line
|
||||
"""
|
||||
|
||||
assert (len(to_transfer_points) == len(to_transfer_points_origin)
|
||||
or len(to_transfer_points_origin) == 0)
|
||||
assert ((overnext_neighbor and not offset_by_half) or not overnext_neighbor)
|
||||
assert (not transfer_forbidden_points or transfer_forbidden_points and (
|
||||
offset_by_half or not offset_by_half and overnext_neighbor))
|
||||
|
||||
if len(to_transfer_points) < 3:
|
||||
return
|
||||
|
||||
current_node = tree.nodes[node]
|
||||
|
||||
# Get a list of all possible adjacent nodes which will be considered for transferring the points of treenode:
|
||||
childs_tuple = tuple(tree.successors(node))
|
||||
if current_node.parent:
|
||||
siblings_tuple = tuple(child for child in tree[current_node.parent] if child != node)
|
||||
else:
|
||||
siblings_tuple = ()
|
||||
|
||||
# Take only neighbors which have not rastered before
|
||||
# We need to distinguish between childs (project towards inner) and parent/siblings (project towards outer)
|
||||
child_list = []
|
||||
child_list_forbidden = []
|
||||
neighbor_list = []
|
||||
neighbor_list_forbidden = []
|
||||
|
||||
if transfer_to_child:
|
||||
for child in childs_tuple:
|
||||
if not tree.nodes[child].already_rastered:
|
||||
if not overnext_neighbor:
|
||||
child_list.append(child)
|
||||
if transfer_forbidden_points:
|
||||
child_list_forbidden.append(child)
|
||||
if overnext_neighbor:
|
||||
for grandchild in tree[child]:
|
||||
if not tree.nodes[grandchild].already_rastered:
|
||||
child_list.append(grandchild)
|
||||
|
||||
if transfer_to_sibling:
|
||||
for sibling in siblings_tuple:
|
||||
if not tree.nodes[sibling].already_rastered:
|
||||
if not overnext_neighbor:
|
||||
neighbor_list.append(sibling)
|
||||
if transfer_forbidden_points:
|
||||
neighbor_list_forbidden.append(sibling)
|
||||
if overnext_neighbor:
|
||||
for nibling in tree[sibling]:
|
||||
if not tree.nodes[nibling].already_rastered:
|
||||
neighbor_list.append(nibling)
|
||||
|
||||
if transfer_to_parent and current_node.parent is not None:
|
||||
if not tree.nodes[current_node.parent].already_rastered:
|
||||
if not overnext_neighbor:
|
||||
neighbor_list.append(current_node.parent)
|
||||
if transfer_forbidden_points:
|
||||
neighbor_list_forbidden.append(current_node.parent)
|
||||
if overnext_neighbor:
|
||||
grandparent = tree.nodes[current_node].parent
|
||||
if grandparent is not None:
|
||||
if not tree.nodes[grandparent].already_rastered:
|
||||
neighbor_list.append(grandparent)
|
||||
|
||||
if not neighbor_list and not child_list:
|
||||
return
|
||||
|
||||
# Go through all rastered points of treenode and check where they should be transferred to its neighbar
|
||||
point_list = list(MultiPoint(to_transfer_points))
|
||||
point_source_list = to_transfer_points_origin.copy()
|
||||
|
||||
# For a linear ring the last point is the same as the starting point which we delete
|
||||
# since we do not want to transfer the starting and end point twice
|
||||
closed_line = LineString(to_transfer_points)
|
||||
if point_list[0].distance(point_list[-1]) < constants.point_spacing_to_be_considered_equal:
|
||||
point_list.pop()
|
||||
if point_source_list:
|
||||
point_source_list.pop()
|
||||
if len(point_list) == 0:
|
||||
return
|
||||
else:
|
||||
# closed line is needed if we offset by half since we need to determine the line
|
||||
# length including the closing segment
|
||||
closed_line = LinearRing(to_transfer_points)
|
||||
|
||||
bisectorline_length = abs(used_offset) * constants.transfer_point_distance_factor * (2.0 if overnext_neighbor else 1.0)
|
||||
|
||||
bisectorline_length_forbidden_points = abs(used_offset) * constants.transfer_point_distance_factor
|
||||
|
||||
linesign_child = math.copysign(1, used_offset)
|
||||
|
||||
i = 0
|
||||
currentDistance = 0
|
||||
while i < len(point_list):
|
||||
assert(point_source_list[i] !=
|
||||
sample_linestring.PointSource.ENTER_LEAVING_POINT)
|
||||
|
||||
# We create a bisecting line through the current point
|
||||
normalized_vector_prev_x = (
|
||||
point_list[i].coords[0][0]-point_list[i-1].coords[0][0]) # makes use of closed shape
|
||||
normalized_vector_prev_y = (
|
||||
point_list[i].coords[0][1]-point_list[i-1].coords[0][1])
|
||||
prev_spacing = math.sqrt(normalized_vector_prev_x*normalized_vector_prev_x +
|
||||
normalized_vector_prev_y*normalized_vector_prev_y)
|
||||
|
||||
normalized_vector_prev_x /= prev_spacing
|
||||
normalized_vector_prev_y /= prev_spacing
|
||||
|
||||
normalized_vector_next_x = normalized_vector_next_y = 0
|
||||
next_spacing = 0
|
||||
while True:
|
||||
normalized_vector_next_x = (
|
||||
point_list[i].coords[0][0]-point_list[(i+1) % len(point_list)].coords[0][0])
|
||||
normalized_vector_next_y = (
|
||||
point_list[i].coords[0][1]-point_list[(i+1) % len(point_list)].coords[0][1])
|
||||
next_spacing = math.sqrt(normalized_vector_next_x*normalized_vector_next_x +
|
||||
normalized_vector_next_y*normalized_vector_next_y)
|
||||
if next_spacing < constants.line_lengh_seen_as_one_point:
|
||||
point_list.pop(i)
|
||||
if(point_source_list):
|
||||
point_source_list.pop(i)
|
||||
currentDistance += next_spacing
|
||||
continue
|
||||
|
||||
normalized_vector_next_x /= next_spacing
|
||||
normalized_vector_next_y /= next_spacing
|
||||
break
|
||||
|
||||
vecx = (normalized_vector_next_x+normalized_vector_prev_x)
|
||||
vecy = (normalized_vector_next_y+normalized_vector_prev_y)
|
||||
vec_length = math.sqrt(vecx*vecx+vecy*vecy)
|
||||
|
||||
vecx_forbidden_point = vecx
|
||||
vecy_forbidden_point = vecy
|
||||
|
||||
# The two sides are (anti)parallel - construct normal vector (bisector) manually:
|
||||
# If we offset by half we are offseting normal to the next segment
|
||||
if(vec_length < constants.line_lengh_seen_as_one_point or offset_by_half):
|
||||
vecx = linesign_child*bisectorline_length*normalized_vector_next_y
|
||||
vecy = -linesign_child*bisectorline_length*normalized_vector_next_x
|
||||
|
||||
if transfer_forbidden_points:
|
||||
vecx_forbidden_point = linesign_child * \
|
||||
bisectorline_length_forbidden_points*normalized_vector_next_y
|
||||
vecy_forbidden_point = -linesign_child * \
|
||||
bisectorline_length_forbidden_points*normalized_vector_next_x
|
||||
|
||||
else:
|
||||
vecx *= bisectorline_length/vec_length
|
||||
vecy *= bisectorline_length/vec_length
|
||||
|
||||
if (vecx*normalized_vector_next_y-vecy * normalized_vector_next_x)*linesign_child < 0:
|
||||
vecx = -vecx
|
||||
vecy = -vecy
|
||||
vecx_forbidden_point = vecx
|
||||
vecy_forbidden_point = vecy
|
||||
|
||||
assert((vecx*normalized_vector_next_y-vecy *
|
||||
normalized_vector_next_x)*linesign_child >= 0)
|
||||
|
||||
originPoint = point_list[i]
|
||||
originPoint_forbidden_point = point_list[i]
|
||||
if(offset_by_half):
|
||||
off = currentDistance+next_spacing/2
|
||||
if off > closed_line.length:
|
||||
off -= closed_line.length
|
||||
originPoint = closed_line.interpolate(off)
|
||||
|
||||
bisectorline_child = LineString([(originPoint.coords[0][0],
|
||||
originPoint.coords[0][1]),
|
||||
(originPoint.coords[0][0]+vecx,
|
||||
originPoint.coords[0][1]+vecy)])
|
||||
|
||||
bisectorline_neighbor = LineString([(originPoint.coords[0][0],
|
||||
originPoint.coords[0][1]),
|
||||
(originPoint.coords[0][0]-vecx,
|
||||
originPoint.coords[0][1]-vecy)])
|
||||
|
||||
bisectorline_forbidden_point_child = LineString([(originPoint_forbidden_point.coords[0][0],
|
||||
originPoint_forbidden_point.coords[0][1]),
|
||||
(originPoint_forbidden_point.coords[0][0]+vecx_forbidden_point,
|
||||
originPoint_forbidden_point.coords[0][1]+vecy_forbidden_point)])
|
||||
|
||||
bisectorline_forbidden_point_neighbor = LineString([(originPoint_forbidden_point.coords[0][0],
|
||||
originPoint_forbidden_point.coords[0][1]),
|
||||
(originPoint_forbidden_point.coords[0][0]-vecx_forbidden_point,
|
||||
originPoint_forbidden_point.coords[0][1]-vecy_forbidden_point)])
|
||||
|
||||
for child in child_list:
|
||||
current_child = tree.nodes[child]
|
||||
point, priority = calc_transferred_point(bisectorline_child, current_child)
|
||||
if point is None:
|
||||
continue
|
||||
current_child.transferred_point_priority_deque.insert(projected_point_tuple(
|
||||
point=point, point_source=sample_linestring.PointSource.OVERNEXT if overnext_neighbor
|
||||
else sample_linestring.PointSource.DIRECT), priority)
|
||||
for child in child_list_forbidden:
|
||||
current_child = tree.nodes[child]
|
||||
point, priority = calc_transferred_point(bisectorline_forbidden_point_child, current_child)
|
||||
if point is None:
|
||||
continue
|
||||
current_child.transferred_point_priority_deque.insert(projected_point_tuple(
|
||||
point=point, point_source=sample_linestring.PointSource.FORBIDDEN_POINT), priority)
|
||||
|
||||
for neighbor in neighbor_list:
|
||||
current_neighbor = tree.nodes[neighbor]
|
||||
point, priority = calc_transferred_point(bisectorline_neighbor, current_neighbor)
|
||||
if point is None:
|
||||
continue
|
||||
current_neighbor.transferred_point_priority_deque.insert(projected_point_tuple(
|
||||
point=point, point_source=sample_linestring.PointSource.OVERNEXT if overnext_neighbor
|
||||
else sample_linestring.PointSource.DIRECT), priority)
|
||||
for neighbor in neighbor_list_forbidden:
|
||||
current_neighbor = tree.nodes[neighbor]
|
||||
point, priority = calc_transferred_point(bisectorline_forbidden_point_neighbor, current_neighbor)
|
||||
if point is None:
|
||||
continue
|
||||
current_neighbor.transferred_point_priority_deque.insert(projected_point_tuple(
|
||||
point=point, point_source=sample_linestring.PointSource.FORBIDDEN_POINT), priority)
|
||||
|
||||
i += 1
|
||||
currentDistance += next_spacing
|
||||
|
||||
assert(len(point_list) == len(point_source_list))
|
||||
|
||||
|
||||
# Calculates the nearest interserction point of "bisectorline" with the coordinates of child.
|
||||
# It returns the intersection point and its distance along the coordinates of the child or "None, None" if no
|
||||
# intersection was found.
|
||||
def calc_transferred_point_graph(bisectorline, edge_geometry):
|
||||
result = bisectorline.intersection(edge_geometry)
|
||||
if result.is_empty:
|
||||
return None, None
|
||||
desired_point = Point()
|
||||
if result.geom_type == 'Point':
|
||||
desired_point = result
|
||||
elif result.geom_type == 'LineString':
|
||||
desired_point = Point(result.coords[0])
|
||||
else:
|
||||
resultlist = list(result)
|
||||
desired_point = resultlist[0]
|
||||
if len(resultlist) > 1:
|
||||
desired_point = nearest_points(
|
||||
result, Point(bisectorline.coords[0]))[0]
|
||||
|
||||
priority = edge_geometry.project(desired_point)
|
||||
point = desired_point
|
||||
return point, priority
|
||||
|
||||
|
||||
def transfer_points_to_surrounding_graph(fill_stitch_graph, current_edge, used_offset, offset_by_half, to_transfer_points, # noqa: C901
|
||||
overnext_neighbor=False, transfer_forbidden_points=False, transfer_to_previous=True, transfer_to_next=True):
|
||||
"""
|
||||
Takes the current graph edge and its rastered points (to_transfer_points) and transfers these points to its previous and next edges (if selected)
|
||||
To do so it calculates the current normal and determines its intersection with the neighbors which gives the transferred points.
|
||||
Input:
|
||||
-fill_stitch_graph: Graph data structure of the stitching lines
|
||||
-current_edge: Current graph edge whose neighbors in fill_stitch_graph shall be considered
|
||||
-used_offset: The used offset when the curves where offsetted
|
||||
-offset_by_half: True if the transferred points shall be interlaced with respect to the points in "to_transfer_points"
|
||||
-to_transfer_points: List of points belonging to treenode which shall be transferred - it is assumed that to_transfer_points
|
||||
can be handled as closed ring
|
||||
-overnext_neighbor: Transfer the points to the overnext neighbor (gives a more stable interlacing)
|
||||
-transfer_forbidden_points: Only allowed for interlacing (offset_by_half): Might be used to transfer points unshifted as
|
||||
forbidden points to the neighbor to avoid a point placing there
|
||||
-transfer_to_previous: If True, points will be transferred to the previous edge in the graph
|
||||
-transfer_to_next: If True, points will be transferred to the next edge in the graph
|
||||
Output:
|
||||
-Fills the attribute "transferred_point_priority_deque" of the next/previous edges. An item of the deque
|
||||
is setup as follows: ((projected point on line, LineStringSampling.PointSource), priority=distance along line)
|
||||
index of point_origin is the index of the point in the neighboring line
|
||||
"""
|
||||
|
||||
assert((overnext_neighbor and not offset_by_half) or not overnext_neighbor)
|
||||
assert(not transfer_forbidden_points or transfer_forbidden_points and (
|
||||
offset_by_half or not offset_by_half and overnext_neighbor))
|
||||
|
||||
if len(to_transfer_points) == 0:
|
||||
return
|
||||
|
||||
# Take only neighbors which have not rastered before
|
||||
# We need to distinguish between childs (project towards inner) and parent/siblings (project towards outer)
|
||||
previous_edge_list = []
|
||||
previous_edge_list_forbidden = []
|
||||
next_edge_list = []
|
||||
next_edge_list_forbidden = []
|
||||
|
||||
if transfer_to_previous:
|
||||
previous_neighbors_tuples = current_edge['previous_neighbors']
|
||||
for neighbor in previous_neighbors_tuples:
|
||||
neighbor_edge = fill_stitch_graph[neighbor[0]
|
||||
][neighbor[-1]]['segment']
|
||||
if not neighbor_edge['already_rastered']:
|
||||
if not overnext_neighbor:
|
||||
previous_edge_list.append(neighbor_edge)
|
||||
if transfer_forbidden_points:
|
||||
previous_edge_list_forbidden.append(neighbor_edge)
|
||||
if overnext_neighbor:
|
||||
overnext_previous_neighbors_tuples = neighbor_edge['previous_neighbors']
|
||||
for overnext_neighbor in overnext_previous_neighbors_tuples:
|
||||
overnext_neighbor_edge = fill_stitch_graph[overnext_neighbor[0]
|
||||
][overnext_neighbor[-1]]['segment']
|
||||
if not overnext_neighbor_edge['already_rastered']:
|
||||
previous_edge_list.append(overnext_neighbor_edge)
|
||||
|
||||
if transfer_to_next:
|
||||
next_neighbors_tuples = current_edge['next_neighbors']
|
||||
for neighbor in next_neighbors_tuples:
|
||||
neighbor_edge = fill_stitch_graph[neighbor[0]
|
||||
][neighbor[-1]]['segment']
|
||||
if not neighbor_edge['already_rastered']:
|
||||
if not overnext_neighbor:
|
||||
next_edge_list.append(neighbor_edge)
|
||||
if transfer_forbidden_points:
|
||||
next_edge_list_forbidden.append(neighbor_edge)
|
||||
if overnext_neighbor:
|
||||
overnext_next_neighbors_tuples = neighbor_edge['next_neighbors']
|
||||
for overnext_neighbor in overnext_next_neighbors_tuples:
|
||||
overnext_neighbor_edge = fill_stitch_graph[overnext_neighbor[0]
|
||||
][overnext_neighbor[-1]]['segment']
|
||||
if not overnext_neighbor_edge['already_rastered']:
|
||||
next_edge_list.append(overnext_neighbor_edge)
|
||||
|
||||
if not previous_edge_list and not next_edge_list:
|
||||
return
|
||||
|
||||
# Go through all rastered points of treenode and check where they should be transferred to its neighbar
|
||||
point_list = list(MultiPoint(to_transfer_points))
|
||||
line = LineString(to_transfer_points)
|
||||
|
||||
bisectorline_length = abs(used_offset) * \
|
||||
constants.transfer_point_distance_factor * \
|
||||
(2.0 if overnext_neighbor else 1.0)
|
||||
|
||||
bisectorline_length_forbidden_points = abs(used_offset) * \
|
||||
constants.transfer_point_distance_factor
|
||||
|
||||
linesign_child = math.copysign(1, used_offset)
|
||||
|
||||
i = 0
|
||||
currentDistance = 0
|
||||
while i < len(point_list):
|
||||
|
||||
# We create a bisecting line through the current point
|
||||
normalized_vector_prev_x = (
|
||||
point_list[i].coords[0][0]-point_list[i-1].coords[0][0]) # makes use of closed shape
|
||||
normalized_vector_prev_y = (
|
||||
point_list[i].coords[0][1]-point_list[i-1].coords[0][1])
|
||||
prev_spacing = math.sqrt(normalized_vector_prev_x*normalized_vector_prev_x +
|
||||
normalized_vector_prev_y*normalized_vector_prev_y)
|
||||
|
||||
normalized_vector_prev_x /= prev_spacing
|
||||
normalized_vector_prev_y /= prev_spacing
|
||||
|
||||
normalized_vector_next_x = normalized_vector_next_y = 0
|
||||
next_spacing = 0
|
||||
while True:
|
||||
normalized_vector_next_x = (
|
||||
point_list[i].coords[0][0]-point_list[(i+1) % len(point_list)].coords[0][0])
|
||||
normalized_vector_next_y = (
|
||||
point_list[i].coords[0][1]-point_list[(i+1) % len(point_list)].coords[0][1])
|
||||
next_spacing = math.sqrt(normalized_vector_next_x*normalized_vector_next_x +
|
||||
normalized_vector_next_y*normalized_vector_next_y)
|
||||
if next_spacing < constants.line_lengh_seen_as_one_point:
|
||||
point_list.pop(i)
|
||||
currentDistance += next_spacing
|
||||
continue
|
||||
|
||||
normalized_vector_next_x /= next_spacing
|
||||
normalized_vector_next_y /= next_spacing
|
||||
break
|
||||
|
||||
vecx = (normalized_vector_next_x+normalized_vector_prev_x)
|
||||
vecy = (normalized_vector_next_y+normalized_vector_prev_y)
|
||||
vec_length = math.sqrt(vecx*vecx+vecy*vecy)
|
||||
|
||||
vecx_forbidden_point = vecx
|
||||
vecy_forbidden_point = vecy
|
||||
|
||||
# The two sides are (anti)parallel - construct normal vector (bisector) manually:
|
||||
# If we offset by half we are offseting normal to the next segment
|
||||
if(vec_length < constants.line_lengh_seen_as_one_point or offset_by_half):
|
||||
vecx = linesign_child*bisectorline_length*normalized_vector_next_y
|
||||
vecy = -linesign_child*bisectorline_length*normalized_vector_next_x
|
||||
|
||||
if transfer_forbidden_points:
|
||||
vecx_forbidden_point = linesign_child * \
|
||||
bisectorline_length_forbidden_points*normalized_vector_next_y
|
||||
vecy_forbidden_point = -linesign_child * \
|
||||
bisectorline_length_forbidden_points*normalized_vector_next_x
|
||||
|
||||
else:
|
||||
vecx *= bisectorline_length/vec_length
|
||||
vecy *= bisectorline_length/vec_length
|
||||
|
||||
if (vecx*normalized_vector_next_y-vecy * normalized_vector_next_x)*linesign_child < 0:
|
||||
vecx = -vecx
|
||||
vecy = -vecy
|
||||
vecx_forbidden_point = vecx
|
||||
vecy_forbidden_point = vecy
|
||||
|
||||
assert((vecx*normalized_vector_next_y-vecy *
|
||||
normalized_vector_next_x)*linesign_child >= 0)
|
||||
|
||||
originPoint = point_list[i]
|
||||
originPoint_forbidden_point = point_list[i]
|
||||
if(offset_by_half):
|
||||
off = currentDistance+next_spacing/2
|
||||
if off > line.length:
|
||||
break
|
||||
originPoint = line.interpolate(off)
|
||||
|
||||
bisectorline = LineString([(originPoint.coords[0][0]-vecx,
|
||||
originPoint.coords[0][1]-vecy),
|
||||
(originPoint.coords[0][0]+vecx,
|
||||
originPoint.coords[0][1]+vecy)])
|
||||
|
||||
bisectorline_forbidden_point = LineString([(originPoint_forbidden_point.coords[0][0]-vecx_forbidden_point,
|
||||
originPoint_forbidden_point.coords[0][1]-vecy_forbidden_point),
|
||||
(originPoint_forbidden_point.coords[0][0]+vecx_forbidden_point,
|
||||
originPoint_forbidden_point.coords[0][1]+vecy_forbidden_point)])
|
||||
|
||||
for edge in previous_edge_list+next_edge_list:
|
||||
point, priority = calc_transferred_point_graph(
|
||||
bisectorline, edge['geometry'])
|
||||
if point is None:
|
||||
continue
|
||||
edge['projected_points'].insert(projected_point_tuple(
|
||||
point=point, point_source=sample_linestring.PointSource.OVERNEXT if overnext_neighbor
|
||||
else sample_linestring.PointSource.DIRECT), priority)
|
||||
for edge_forbidden in previous_edge_list_forbidden+next_edge_list_forbidden:
|
||||
point, priority = calc_transferred_point_graph(
|
||||
bisectorline_forbidden_point, edge_forbidden['geometry'])
|
||||
if point is None:
|
||||
continue
|
||||
edge_forbidden['projected_points'].insert(projected_point_tuple(
|
||||
point=point, point_source=sample_linestring.PointSource.FORBIDDEN_POINT), priority)
|
||||
|
||||
i += 1
|
||||
currentDistance += next_spacing
|
|
@ -1,342 +0,0 @@
|
|||
from enum import IntEnum
|
||||
|
||||
import numpy as np
|
||||
from shapely.geometry import LineString, Point
|
||||
from shapely.ops import substring
|
||||
|
||||
from ..stitches import constants, point_transfer
|
||||
|
||||
|
||||
class PointSource(IntEnum):
|
||||
"""
|
||||
Used to tag the origin of a rastered point
|
||||
"""
|
||||
# MUST_USE = 0 # Legacy
|
||||
REGULAR_SPACING = 1 # introduced to not exceed maximal stichting distance
|
||||
# INITIAL_RASTERING = 2 #Legacy
|
||||
# point which must be stitched to avoid to large deviations to the desired path
|
||||
EDGE_NEEDED = 3
|
||||
# NOT_NEEDED = 4 #Legacy
|
||||
# ALREADY_TRANSFERRED = 5 #Legacy
|
||||
# ADDITIONAL_TRACKING_POINT_NOT_NEEDED = 6 #Legacy
|
||||
# EDGE_RASTERING_ALLOWED = 7 #Legacy
|
||||
# EDGE_PREVIOUSLY_SHIFTED = 8 #Legacy
|
||||
ENTER_LEAVING_POINT = 9 # Whether this point is used to enter or leave a child
|
||||
# If the angle at a point is <= constants.limiting_angle this point is marked as SOFT_EDGE
|
||||
SOFT_EDGE_INTERNAL = 10
|
||||
# If the angle at a point is > constants.limiting_angle this point is marked as HARD_EDGE (HARD_EDGES will always be stitched)
|
||||
HARD_EDGE_INTERNAL = 11
|
||||
# If the point was created by a projection (transferred point) of a neighbor it is marked as PROJECTED_POINT
|
||||
PROJECTED_POINT = 12
|
||||
REGULAR_SPACING_INTERNAL = 13 # introduced to not exceed maximal stichting distance
|
||||
# FORBIDDEN_POINT_INTERNAL=14 #Legacy
|
||||
SOFT_EDGE = 15 # If the angle at a point is <= constants.limiting_angle this point is marked as SOFT_EDGE
|
||||
# If the angle at a point is > constants.limiting_angle this point is marked as HARD_EDGE (HARD_EDGES will always be stitched)
|
||||
HARD_EDGE = 16
|
||||
FORBIDDEN_POINT = 17 # Only relevant for desired interlacing - non-shifted point positions at the next neighbor are marked as forbidden
|
||||
# If one decides to avoid forbidden points new points to the left and to the right as replacement are created
|
||||
REPLACED_FORBIDDEN_POINT = 18
|
||||
DIRECT = 19 # Calculated by next neighbor projection
|
||||
OVERNEXT = 20 # Calculated by overnext neighbor projection
|
||||
|
||||
|
||||
def calculate_line_angles(line):
|
||||
"""
|
||||
Calculates the angles between adjacent edges at each interior point
|
||||
Note that the first and last values in the return array are zero since for the boundary points no
|
||||
angle calculations were possible
|
||||
"""
|
||||
angles = np.zeros(len(line.coords))
|
||||
|
||||
# approach from https://stackoverflow.com/a/50772253/4249120
|
||||
vectors = np.diff(line.coords, axis=0)
|
||||
v1 = vectors[:-1]
|
||||
v2 = vectors[1:]
|
||||
dot = np.einsum('ij,ij->i', v1, v2)
|
||||
mag1 = np.linalg.norm(v1, axis=1)
|
||||
mag2 = np.linalg.norm(v2, axis=1)
|
||||
cosines = dot / (mag1 * mag2)
|
||||
angles[1:-1] = np.arccos(np.clip(cosines, -1, 1))
|
||||
|
||||
return angles
|
||||
|
||||
|
||||
def raster_line_string_with_priority_points(line, # noqa: C901
|
||||
start_distance,
|
||||
end_distance,
|
||||
maxstitch_distance,
|
||||
minstitch_distance,
|
||||
must_use_points_deque,
|
||||
abs_offset,
|
||||
offset_by_half,
|
||||
replace_forbidden_points):
|
||||
"""
|
||||
Rasters a line between start_distance and end_distance.
|
||||
Input:
|
||||
-line: The line to be rastered
|
||||
-start_distance: The distance along the line from which the rastering should start
|
||||
-end_distance: The distance along the line until which the rastering should be done
|
||||
-maxstitch_distance: The maximum allowed stitch distance
|
||||
-minstitch_distance: The minimum allowed stitch distance
|
||||
-Note that start_distance > end_distance for stitching_direction = -1
|
||||
-must_use_points_deque: deque with projected points on line from its neighbors. An item of the deque
|
||||
is setup as follows: ((projected point on line, LineStringSampling.PointSource), priority=distance along line)
|
||||
index of point_origin is the index of the point in the neighboring line
|
||||
-abs_offset: used offset between to offsetted curves
|
||||
-offset_by_half: Whether the points of neighboring lines shall be interlaced or not
|
||||
-replace_forbidden_points: Whether points marked as forbidden in must_use_points_deque shall be replaced by adjacend points
|
||||
Output:
|
||||
-List of tuples with the rastered point coordinates
|
||||
-List which defines the point origin for each point according to the PointSource enum.
|
||||
"""
|
||||
|
||||
if (abs(end_distance-start_distance) < max(minstitch_distance, constants.line_lengh_seen_as_one_point)):
|
||||
return [line.interpolate(start_distance).coords[0]], [PointSource.HARD_EDGE]
|
||||
|
||||
deque_points = list(must_use_points_deque)
|
||||
|
||||
linecoords = line.coords
|
||||
|
||||
if start_distance > end_distance:
|
||||
start_distance, end_distance = line.length - \
|
||||
start_distance, line.length - end_distance
|
||||
linecoords = linecoords[::-1]
|
||||
for i in range(len(deque_points)):
|
||||
deque_points[i] = (deque_points[i][0],
|
||||
line.length - deque_points[i][1])
|
||||
else:
|
||||
# Since points with highest priority (=distance along line) are first (descending sorted)
|
||||
deque_points = deque_points[::-1]
|
||||
|
||||
# Remove all points from the deque which do not fall in the segment [start_distance; end_distance]
|
||||
while (len(deque_points) > 0 and
|
||||
deque_points[0][1] <= start_distance + min(maxstitch_distance / 20, minstitch_distance, constants.point_spacing_to_be_considered_equal)):
|
||||
deque_points.pop(0)
|
||||
while (len(deque_points) > 0 and
|
||||
deque_points[-1][1] >= end_distance - min(maxstitch_distance / 20, minstitch_distance, constants.point_spacing_to_be_considered_equal)):
|
||||
deque_points.pop()
|
||||
|
||||
# Ordering in priority queue:
|
||||
# (point, LineStringSampling.PointSource), priority)
|
||||
# might be different from line for stitching_direction=-1
|
||||
aligned_line = LineString(linecoords)
|
||||
path_coords = substring(aligned_line,
|
||||
start_distance, end_distance)
|
||||
|
||||
# aligned line is a line without doubled points.
|
||||
# I had the strange situation in which the offset "start_distance" from the line beginning
|
||||
# resulted in a starting point which was already present in aligned_line causing a doubled point.
|
||||
# A double point is not allowed in the following calculations so we need to remove it:
|
||||
if (abs(path_coords.coords[0][0] - path_coords.coords[1][0]) < constants.eps and
|
||||
abs(path_coords.coords[0][1] - path_coords.coords[1][1]) < constants.eps):
|
||||
path_coords.coords = path_coords.coords[1:]
|
||||
if (abs(path_coords.coords[-1][0] - path_coords.coords[-2][0]) < constants.eps and
|
||||
abs(path_coords.coords[-1][1] - path_coords.coords[-2][1]) < constants.eps):
|
||||
path_coords.coords = path_coords.coords[:-1]
|
||||
|
||||
angles = calculate_line_angles(path_coords)
|
||||
# For the first and last point we cannot calculate an angle. Set it to above the limit to make it a hard edge
|
||||
angles[0] = 1.1 * constants.limiting_angle
|
||||
angles[-1] = 1.1 * constants.limiting_angle
|
||||
|
||||
current_distance = 0
|
||||
last_point = Point(path_coords.coords[0])
|
||||
# Next we merge the line points and the projected (deque) points into one list
|
||||
merged_point_list = []
|
||||
dq_iter = 0
|
||||
for point, angle in zip(path_coords.coords, angles):
|
||||
current_distance += last_point.distance(Point(point))
|
||||
last_point = Point(point)
|
||||
while dq_iter < len(deque_points) and deque_points[dq_iter][1] < current_distance+start_distance:
|
||||
# We want to avoid setting points at soft edges close to forbidden points
|
||||
if deque_points[dq_iter][0].point_source == PointSource.FORBIDDEN_POINT:
|
||||
# Check whether a previous added point is a soft edge close to the forbidden point
|
||||
if (merged_point_list[-1][0].point_source == PointSource.SOFT_EDGE_INTERNAL and
|
||||
abs(merged_point_list[-1][1]-deque_points[dq_iter][1]+start_distance < abs_offset*constants.factor_offset_forbidden_point)):
|
||||
item = merged_point_list.pop()
|
||||
merged_point_list.append((point_transfer.projected_point_tuple(
|
||||
point=item[0].point, point_source=PointSource.FORBIDDEN_POINT), item[1]-start_distance))
|
||||
else:
|
||||
merged_point_list.append(
|
||||
(deque_points[dq_iter][0], deque_points[dq_iter][1]-start_distance))
|
||||
# merged_point_list.append(deque_points[dq_iter])
|
||||
dq_iter += 1
|
||||
# Check whether the current point is close to a forbidden point
|
||||
if (dq_iter < len(deque_points) and
|
||||
deque_points[dq_iter-1][0].point_source == PointSource.FORBIDDEN_POINT and
|
||||
angle < constants.limiting_angle and
|
||||
abs(deque_points[dq_iter-1][1]-current_distance-start_distance) < abs_offset*constants.factor_offset_forbidden_point):
|
||||
point_source = PointSource.FORBIDDEN_POINT
|
||||
else:
|
||||
if angle < constants.limiting_angle:
|
||||
point_source = PointSource.SOFT_EDGE_INTERNAL
|
||||
else:
|
||||
point_source = PointSource.HARD_EDGE_INTERNAL
|
||||
merged_point_list.append((point_transfer.projected_point_tuple(
|
||||
point=Point(point), point_source=point_source), current_distance))
|
||||
|
||||
result_list = [merged_point_list[0]]
|
||||
|
||||
# General idea: Take one point of merged_point_list after another into the current segment until this segment is not simplified
|
||||
# to a straight line by shapelys simplify method.
|
||||
# Then, look at the points within this segment and choose the best fitting one
|
||||
# (HARD_EDGE > OVERNEXT projected point > DIRECT projected point) as termination of this segment
|
||||
# and start point for the next segment (so we do not always take the maximum possible length for a segment)
|
||||
segment_start_index = 0
|
||||
segment_end_index = 1
|
||||
forbidden_point_list = []
|
||||
while segment_end_index < len(merged_point_list):
|
||||
# Collection of points for the current segment
|
||||
current_point_list = [merged_point_list[segment_start_index][0].point]
|
||||
|
||||
while segment_end_index < len(merged_point_list):
|
||||
segment_length = merged_point_list[segment_end_index][1] - \
|
||||
merged_point_list[segment_start_index][1]
|
||||
if segment_length < minstitch_distance:
|
||||
segment_end_index += 1
|
||||
continue
|
||||
if segment_length > maxstitch_distance+constants.point_spacing_to_be_considered_equal:
|
||||
new_distance = merged_point_list[segment_start_index][1] + \
|
||||
maxstitch_distance
|
||||
merged_point_list.insert(segment_end_index, (point_transfer.projected_point_tuple(
|
||||
point=aligned_line.interpolate(new_distance), point_source=PointSource.REGULAR_SPACING_INTERNAL), new_distance))
|
||||
segment_end_index += 1
|
||||
break
|
||||
|
||||
current_point_list.append(
|
||||
merged_point_list[segment_end_index][0].point)
|
||||
simplified_len = len(LineString(current_point_list).simplify(
|
||||
constants.factor_offset_remove_dense_points*abs_offset, preserve_topology=False).coords)
|
||||
if simplified_len > 2: # not all points have been simplified - so we need to add it
|
||||
break
|
||||
|
||||
if merged_point_list[segment_end_index][0].point_source == PointSource.HARD_EDGE_INTERNAL:
|
||||
segment_end_index += 1
|
||||
break
|
||||
segment_end_index += 1
|
||||
|
||||
segment_end_index -= 1
|
||||
|
||||
# Now we choose the best fitting point within this segment
|
||||
index_overnext = -1
|
||||
index_direct = -1
|
||||
index_hard_edge = -1
|
||||
|
||||
iter = segment_start_index+1
|
||||
while (iter <= segment_end_index):
|
||||
segment_length = merged_point_list[iter][1] - \
|
||||
merged_point_list[segment_start_index][1]
|
||||
if segment_length < minstitch_distance and merged_point_list[iter][0].point_source != PointSource.HARD_EDGE_INTERNAL:
|
||||
# We need to create this hard edge exception - otherwise there are some too large deviations posible
|
||||
iter += 1
|
||||
continue
|
||||
|
||||
if merged_point_list[iter][0].point_source == PointSource.OVERNEXT:
|
||||
index_overnext = iter
|
||||
elif merged_point_list[iter][0].point_source == PointSource.DIRECT:
|
||||
index_direct = iter
|
||||
elif merged_point_list[iter][0].point_source == PointSource.HARD_EDGE_INTERNAL:
|
||||
index_hard_edge = iter
|
||||
iter += 1
|
||||
if index_hard_edge != -1:
|
||||
segment_end_index = index_hard_edge
|
||||
else:
|
||||
if offset_by_half:
|
||||
index_preferred = index_overnext
|
||||
index_less_preferred = index_direct
|
||||
else:
|
||||
index_preferred = index_direct
|
||||
index_less_preferred = index_overnext
|
||||
|
||||
if index_preferred != -1:
|
||||
if (index_less_preferred != -1 and index_less_preferred > index_preferred and
|
||||
(merged_point_list[index_less_preferred][1]-merged_point_list[index_preferred][1]) >=
|
||||
constants.factor_segment_length_direct_preferred_over_overnext *
|
||||
(merged_point_list[index_preferred][1]-merged_point_list[segment_start_index][1])):
|
||||
# We allow to take the direct projected point instead of the overnext projected point if it would result in a
|
||||
# significant longer segment length
|
||||
segment_end_index = index_less_preferred
|
||||
else:
|
||||
segment_end_index = index_preferred
|
||||
elif index_less_preferred != -1:
|
||||
segment_end_index = index_less_preferred
|
||||
|
||||
# Usually OVERNEXT and DIRECT points are close to each other and in some cases both were selected as segment edges
|
||||
# If they are too close (<abs_offset) we remove one of it
|
||||
if (((merged_point_list[segment_start_index][0].point_source == PointSource.OVERNEXT and
|
||||
merged_point_list[segment_end_index][0].point_source == PointSource.DIRECT) or
|
||||
(merged_point_list[segment_start_index][0].point_source == PointSource.DIRECT and
|
||||
merged_point_list[segment_end_index][0].point_source == PointSource.OVERNEXT)) and
|
||||
abs(merged_point_list[segment_end_index][1] - merged_point_list[segment_start_index][1]) < abs_offset):
|
||||
result_list.pop()
|
||||
|
||||
result_list.append(merged_point_list[segment_end_index])
|
||||
# To have a chance to replace all forbidden points afterwards
|
||||
if merged_point_list[segment_end_index][0].point_source == PointSource.FORBIDDEN_POINT:
|
||||
forbidden_point_list.append(len(result_list)-1)
|
||||
|
||||
segment_start_index = segment_end_index
|
||||
segment_end_index += 1
|
||||
|
||||
return_point_list = [] # [result_list[0][0].point.coords[0]]
|
||||
return_point_source_list = [] # [result_list[0][0].point_source]
|
||||
|
||||
# Note: replacement of forbidden points sometimes not satisfying
|
||||
if replace_forbidden_points:
|
||||
result_list = _replace_forbidden_points(
|
||||
aligned_line, result_list, forbidden_point_list, abs_offset)
|
||||
|
||||
# Finally we create the final return_point_list and return_point_source_list
|
||||
for i in range(len(result_list)):
|
||||
return_point_list.append(result_list[i][0].point.coords[0])
|
||||
if result_list[i][0].point_source == PointSource.HARD_EDGE_INTERNAL:
|
||||
point_source = PointSource.HARD_EDGE
|
||||
elif result_list[i][0].point_source == PointSource.SOFT_EDGE_INTERNAL:
|
||||
point_source = PointSource.SOFT_EDGE
|
||||
elif result_list[i][0].point_source == PointSource.REGULAR_SPACING_INTERNAL:
|
||||
point_source = PointSource.REGULAR_SPACING
|
||||
elif result_list[i][0].point_source == PointSource.FORBIDDEN_POINT:
|
||||
point_source = PointSource.FORBIDDEN_POINT
|
||||
else:
|
||||
point_source = PointSource.PROJECTED_POINT
|
||||
|
||||
return_point_source_list.append(point_source)
|
||||
|
||||
assert(len(return_point_list) == len(return_point_source_list))
|
||||
|
||||
# return remove_dense_points(returnpointlist, returnpointsourcelist, maxstitch_distance,abs_offset)
|
||||
return return_point_list, return_point_source_list
|
||||
|
||||
|
||||
def _replace_forbidden_points(line, result_list, forbidden_point_list_indices, abs_offset):
|
||||
# since we add and remove points in the result_list, we need to adjust the indices stored in forbidden_point_list_indices
|
||||
current_index_shift = 0
|
||||
for index in forbidden_point_list_indices:
|
||||
index += current_index_shift
|
||||
distance_left = result_list[index][0].point.distance(
|
||||
result_list[index-1][0].point)/2.0
|
||||
distance_right = result_list[index][0].point.distance(
|
||||
result_list[(index+1) % len(result_list)][0].point)/2.0
|
||||
while distance_left > constants.point_spacing_to_be_considered_equal and distance_right > constants.point_spacing_to_be_considered_equal:
|
||||
new_point_left_proj = result_list[index][1]-distance_left
|
||||
if new_point_left_proj < 0:
|
||||
new_point_left_proj += line.length
|
||||
new_point_right_proj = result_list[index][1]+distance_right
|
||||
if new_point_right_proj > line.length:
|
||||
new_point_right_proj -= line.length
|
||||
point_left = line.interpolate(new_point_left_proj)
|
||||
point_right = line.interpolate(new_point_right_proj)
|
||||
forbidden_point_distance = result_list[index][0].point.distance(
|
||||
LineString([point_left, point_right]))
|
||||
if forbidden_point_distance < constants.factor_offset_remove_dense_points*abs_offset:
|
||||
del result_list[index]
|
||||
result_list.insert(index, (point_transfer.projected_point_tuple(
|
||||
point=point_right, point_source=PointSource.REPLACED_FORBIDDEN_POINT), new_point_right_proj))
|
||||
result_list.insert(index, (point_transfer.projected_point_tuple(
|
||||
point=point_left, point_source=PointSource.REPLACED_FORBIDDEN_POINT), new_point_left_proj))
|
||||
current_index_shift += 1
|
||||
break
|
||||
else:
|
||||
distance_left /= 2.0
|
||||
distance_right /= 2.0
|
||||
return result_list
|
|
@ -10,7 +10,6 @@ from .running_stitch import running_stitch
|
|||
|
||||
from ..debug import debug
|
||||
from ..stitches import constants
|
||||
from ..stitches import sample_linestring
|
||||
from ..stitch_plan import Stitch
|
||||
from ..utils.geometry import cut, roll_linear_ring, reverse_line_string
|
||||
|
||||
|
@ -109,23 +108,6 @@ def create_nearest_points_list(
|
|||
return children_nearest_points
|
||||
|
||||
|
||||
def calculate_replacing_middle_point(line_segment, abs_offset, max_stitch_distance):
|
||||
"""
|
||||
Takes a line segment (consisting of 3 points!)
|
||||
and calculates a new middle point if the line_segment is
|
||||
straight enough to be resampled by points max_stitch_distance apart FROM THE END OF line_segment.
|
||||
Returns None if the middle point is not needed.
|
||||
"""
|
||||
angles = sample_linestring.calculate_line_angles(line_segment)
|
||||
if angles[1] < abs_offset * constants.limiting_angle_straight:
|
||||
if line_segment.length < max_stitch_distance:
|
||||
return None
|
||||
else:
|
||||
return line_segment.interpolate(line_segment.length - max_stitch_distance).coords[0]
|
||||
else:
|
||||
return line_segment.coords[1]
|
||||
|
||||
|
||||
@debug.time
|
||||
def connect_raster_tree_from_inner_to_outer(tree, node, offset, stitch_distance, min_stitch_distance, starting_point,
|
||||
offset_by_half): # noqa: C901
|
||||
|
|
Ładowanie…
Reference in New Issue