summaryrefslogtreecommitdiff
path: root/lib/stitches/sample_linestring.py
diff options
context:
space:
mode:
Diffstat (limited to 'lib/stitches/sample_linestring.py')
-rw-r--r--lib/stitches/sample_linestring.py342
1 files changed, 0 insertions, 342 deletions
diff --git a/lib/stitches/sample_linestring.py b/lib/stitches/sample_linestring.py
deleted file mode 100644
index 65760717..00000000
--- a/lib/stitches/sample_linestring.py
+++ /dev/null
@@ -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