def linestring_intersection(target: LineString, tool: LineString) -> List[float]: """will return a list of parameters for the target where the tool intersects the target. Parameters are a floating point number where 0.0 <= parameter <= len(linestring)-1.0 Parameters that are rounded to the nearest integer are the same as the index of target. Intermediate values represent the interpolated point between vertices on the target linestring.""" intersection_parameters = [] for i, (a, b) in enumerate(pairwise(target)): for j, (c, d) in enumerate(pairwise(tool)): # print(f"{i},{i + 1} against {j + i + 2},{j + i + 1 + 2}") intersection_result = linesegment_intersection(a, b, c, d) if intersection_result is not None: # TODO: collect p to prevent recalculation? p, t1, t2 = intersection_result if 0 <= t1 <= 1 and 0 <= t2 <= 1: param_1 = i + t1 print(param_1) intersection_parameters.append(param_1) last_item = float("inf") output = [] for item in sorted(intersection_parameters): if not math.isclose(last_item, item): output.append(item) last_item = item return output
def linestring_remove_circle(circle_center: Vector2, radius: float, line_string: LineString) -> List[LineString]: results: List[LineString] = [] sub_result: LineString = [] for line_segment in pairwise(line_string): relation, segments = remove_circle_from_linesegment( circle_center, radius, line_segment) if relation == interval_tools.SUB_RESULT_ALL: if len(sub_result) == 0: sub_result.append(line_segment[0]) sub_result.append(line_segment[1]) else: if relation & interval_tools.SUB_RESULT_START: if len(sub_result) == 0: sub_result.append(line_segment[0]) sub_result.append(segments[0][1]) results.append(sub_result) sub_result = [] if relation & interval_tools.SUB_RESULT_END: sub_result.append(segments[-1][0]) sub_result.append(line_segment[1]) if sub_result: results.append(sub_result) return results
def project_point_onto_linestring_distance_along( target: LineString, tool: Vector2) -> Tuple[float, Vector2, float]: """returns the distance to the linestring (squared), and the point on the line, and the distance along the line""" min_mag_squared = float('inf') dist_along_at_min_mag_squared = None point = None dist_along_so_far = 0 for segment in pairwise(target): c = tool a, b = segment # project c onto ab, ab = b - a ac = c - a ab_magnitude = ab.magnitude # p is the projection of c onto ab, clamped between a and b p_scalar = clamp_zero_to_one(ac.dot(ab) / ab.magnitude_squared) p = a + ab.scaled(p_scalar) pc_magnitude_squared = (p - c).magnitude_squared if min_mag_squared > pc_magnitude_squared: point = p min_mag_squared = pc_magnitude_squared dist_along_at_min_mag_squared = dist_along_so_far + p_scalar * ab_magnitude dist_along_so_far += ab_magnitude return min_mag_squared, point, dist_along_at_min_mag_squared
def linestring_cut( linestring: LineString, normalised_distance_along: float ) -> Tuple[Optional[LineString], Optional[LineString]]: """cut linestring at normalised distance along and returns a pair of linestrings""" measured_linestring, total_length = linestring_measure(linestring) distance_along = total_length * normalised_distance_along if distance_along < 0.0 or math.isclose(distance_along, 0.0): return None, linestring if distance_along > total_length or math.isclose(distance_along, total_length): return linestring, None else: distance_remaining = distance_along for index, ((a, segment_length), (b, _)) in enumerate(pairwise(measured_linestring)): if math.isclose(0, distance_remaining): return (linestring[:index + 1], linestring[index:]) elif distance_remaining < segment_length: ab = b - a intermediate_point = a + ab / segment_length * distance_remaining return (linestring[:index + 1] + [intermediate_point], [intermediate_point] + linestring[index + 1:]) distance_remaining -= segment_length
def test_remove_circles_from_linestring(): a = [Vector2(a, b) for a, b in [[-8.86, 0.99], [-4.98, 4.23], [2.86, 2.95], [4.66, 4.03], [3.34, 6.39], [1.98, 7.39], [-2.34, 7.79]]] c = [Vector2(a, b) for a, b in [[-1.98, 4.51], [-0.9, 9.55], [0.98, 9.43], [6.18, 4.19], [2.22, 1.31], [4.74, 7.39], [-6.678259236067627, 2.8118659987476518]]] r = 2 res = remove_circles_from_linestring_2(linestring_measure(a), c, r) result = [] for ls in res: for a,b in pairwise(ls): result.append(f"Segment(({a.x},{a.y}),({b.x},{b.y}))") print(result)
def linestring_interpolate(linestring: List[Vector2], real_distance_along: float): dist = 0 for a, b in pairwise(linestring): ab = b - a ab_len = ab.magnitude if dist + ab_len >= real_distance_along: return a + ab.scaled((real_distance_along - dist) / ab_len) dist += ab_len return a + ab.scaled((real_distance_along - dist + 1) / ab_len)
def linestring_interpolate_normalised(linestring: List[Vector2], normalised_distance: float): linestring_measured, total_length = linestring_measure(linestring) real_distance_along = normalised_distance * total_length dist = 0 for (a, ab_len), (b, _) in pairwise(linestring_measured): ab = b - a if dist + ab_len >= real_distance_along: return a + ab.scaled((real_distance_along - dist) / ab_len) dist += ab_len return a + ab.scaled((real_distance_along - dist + 1) / ab_len)
def linestring_direction(line_string: LineString, normalised_distance_along: float) -> Vector2: """returns the direction (as a unit vector) of a linestring segment which contains the point""" measured_linestring, line_string_length = linestring_measure(line_string) de_normalised_distance_along = line_string_length * normalised_distance_along len_so_far = 0 for (a, ab_len), (b, _) in pairwise(measured_linestring): ab = b - a len_so_far += ab_len if len_so_far >= de_normalised_distance_along: return ab / ab_len return ab / ab_len
def linestring_measure( linestring: List[Vector2] ) -> Tuple[List[Tuple[Vector2, float]], float]: """:returns: ([(point:vector, dist_to_next_point:float), ...], total_length:float)""" result = [] total_length = 0 for a, b in pairwise(linestring): ab_len = (b - a).magnitude result.append((a, ab_len)) total_length += ab_len result.append((b, 0)) return result, total_length
def remove_circles_from_linestring_2(measured_linestring: MeasuredLineString, circle_centers: List[Vector2], radius: float): """Second version of this operation that is substantially optimised""" result_linestrings = [] current_linestring = [] for (a, ab_length), (b, _) in pairwise(measured_linestring[0]): (first_parameter, last_parameter), line_segments = remove_circles_from_linesegment( (a, b), ab_length, circle_centers, radius) if not line_segments: # nothing to add continue elif len(line_segments) == 1: if first_parameter != 0: if current_linestring: result_linestrings.append(current_linestring) current_linestring = [] current_linestring.append(line_segments[0][0]) current_linestring.append(line_segments[0][1]) if last_parameter != 1: result_linestrings.append(current_linestring) current_linestring = [] else: first_segment = line_segments[0] middle_segments = line_segments[1:-1] last_segment = line_segments[-1] if first_parameter == 0: if current_linestring: current_linestring.append(first_segment[1]) else: current_linestring.extend(first_segment) result_linestrings.append(current_linestring) current_linestring = [] else: if current_linestring: result_linestrings.append(current_linestring) current_linestring = [] result_linestrings.append(list(first_segment)) result_linestrings.extend(list(item) for item in middle_segments) if last_parameter == 1: current_linestring.extend(last_segment) else: result_linestrings.append(list(last_segment)) return result_linestrings
def linestring_intersection_with_self(inp: LineString) -> List[float]: intersection_parameters = [] for i, (a, b) in enumerate(pairwise(inp)): for j, (c, d) in enumerate(pairwise(inp[i + 2:])): # print(f"{i},{i + 1} against {j + i + 2},{j + i + 1 + 2}") intersection_result = linesegment_intersection(a, b, c, d) if intersection_result is not None: # TODO: collect p to prevent recalculation? p, t1, t2 = intersection_result if 0 <= t1 <= 1 and 0 <= t2 <= 1: param_1 = i + t1 param_2 = j + i + 2 + t2 # print((param_1, param_2)) intersection_parameters.append(param_1) intersection_parameters.append(param_2) last_item = float("inf") output = [] for item in sorted(intersection_parameters): if not math.isclose(last_item, item): output.append(item) last_item = item return output
def project_point_onto_linestring(target: LineString, tool: Vector2) -> Tuple[float, Vector2]: """returns the distance to the linestring, and the point on the line""" min_mag_squared = float('inf') point = None for segment in pairwise(target): c = tool a, b = segment # project c onto ab, ab = b - a ac = c - a # p is the projection of c onto ab p_scalar = clamp_zero_to_one(ac.dot(ab) / ab.magnitude_squared) p = a + ab.scaled(p_scalar) pc_magnitude_squared = (p - c).magnitude_squared if min_mag_squared > pc_magnitude_squared: point = p min_mag_squared = pc_magnitude_squared return min_mag_squared, point
def linestring_length(linestring: List[Vector2]): result = 0 for a, b in pairwise(linestring): result += (b - a).magnitude return result