求纯Python库或算法:用任意线段精确拆分含线段与圆弧的2D多段线
纯Python实现线段拆分混合多段线的方案
核心思路
要实现精确拆分(不近似圆弧),需分三步处理:先实现基础几何计算工具,再逐个拆分多段线元素,最后重组拆分后的片段为完整多段线列表。
1. 基础几何工具函数
先实现核心几何计算逻辑,用于判断位置、求解交点:
- 点相对拆分线段的位置判断:通过叉积计算,返回点在拆分线某一侧、另一侧或线上的标识
- 线段与线段的精确交点:用线性方程组求解,返回交点坐标(若存在)
- 线段与圆弧的精确交点:先求直线与圆的交点,再验证交点是否落在圆弧的角度范围内
2. 多段线元素拆分逻辑
对多段线的每个元素(线段/圆弧)逐一处理:
- 线段元素:
- 若完全在拆分线一侧:直接归入对应侧的多段线片段
- 若跨拆分线:计算交点,拆分为两段线段,分别归入对应侧
- 圆弧元素:
- 若完全在拆分线一侧:直接归入对应侧的多段线片段
- 若跨拆分线:计算圆弧与拆分线的交点,根据交点角度拆分原圆弧为1-2段圆弧,分别归入对应侧
3. 多段线重组
拆分完成后,跟踪片段的首尾连接关系,将连续片段拼接为完整多段线。若拆分后产生独立的不连续片段,分别归入两侧的多段线列表。
纯Python实现示例框架
import math # 判断点相对于拆分线段的位置:1=一侧,-1=另一侧,0=在线上 def point_side(p, split_line): (x1, y1), (x2, y2) = split_line cross = (x2 - x1) * (p[1] - y1) - (y2 - y1) * (p[0] - x1) if abs(cross) < 1e-9: return 0 return 1 if cross > 0 else -1 # 计算线段与拆分线的交点 def get_segment_split_intersection(seg, split_line): (x1, y1), (x2, y2) = seg (sx1, sy1), (sx2, sy2) = split_line den = (x1 - x2)*(sy1 - sy2) - (y1 - y2)*(sx1 - sx2) if abs(den) < 1e-9: return None # 平行无交点 t_num = (x1 - sx1)*(sy1 - sy2) - (y1 - sy1)*(sx1 - sx2) t = t_num / den u_num = -((x1 - x2)*(y1 - sy1) - (y1 - y2)*(x1 - sx1)) u = u_num / den if 0 <= t <= 1 and 0 <= u <= 1: x = x1 + t*(x2 - x1) y = y1 + t*(y2 - y1) return (round(x, 9), round(y, 9)) return None # 计算圆弧与拆分线的交点列表 def get_arc_split_intersections(arc, split_line): (cx, cy), r, start_angle, end_angle = arc (sx1, sy1), (sx2, sy2) = split_line # 直线的一般式Ax + By + C = 0 A = sy2 - sy1 B = sx1 - sx2 C = sx2*sy1 - sx1*sy2 # 计算圆心到直线的距离 dist = abs(A*cx + B*cy + C) / math.hypot(A, B) if dist > r + 1e-9: return [] # 无交点 elif abs(dist - r) < 1e-9: # 相切,一个交点 x = cx - A*(A*cx + B*cy + C)/(A**2 + B**2) y = cy - B*(A*cx + B*cy + C)/(A**2 + B**2) intersect = (round(x,9), round(y,9)) else: # 两个交点 sqrt_val = math.sqrt(r**2 - dist**2) x1 = cx - A*(A*cx + B*cy + C)/(A**2 + B**2) + B*sqrt_val/(A**2 + B**2) y1 = cy - B*(A*cx + B*cy + C)/(A**2 + B**2) - A*sqrt_val/(A**2 + B**2) x2 = cx - A*(A*cx + B*cy + C)/(A**2 + B**2) - B*sqrt_val/(A**2 + B**2) y2 = cy - B*(A*cx + B*cy + C)/(A**2 + B**2) + A*sqrt_val/(A**2 + B**2) intersect = [(round(x1,9), round(y1,9)), (round(x2,9), round(y2,9))] # 验证交点是否在圆弧角度范围内 valid_intersects = [] theta_dir = 1 if end_angle >= start_angle else -1 start_rad = math.radians(start_angle) end_rad = math.radians(end_angle) def angle_in_range(rad): if theta_dir == 1: return start_rad - 1e-9 <= rad <= end_rad + 1e-9 else: return rad >= start_rad - 1e-9 or rad <= end_rad + 1e-9 for p in (intersect if isinstance(intersect, list) else [intersect]): rad = math.atan2(p[1]-cy, p[0]-cx) if rad < 0: rad += 2*math.pi if angle_in_range(rad): valid_intersects.append(p) return valid_intersects # 拆分多段线主函数 def split_polyline(polyline, split_line): left_segments = [] right_segments = [] current_left = [] current_right = [] for elem in polyline: if len(elem) == 2: # 线段元素 p1, p2 = elem s1 = point_side(p1, split_line) s2 = point_side(p2, split_line) if s1 == s2: if s1 == 1: current_left.append(elem) elif s1 == -1: current_right.append(elem) else: intersect = get_segment_split_intersection(elem, split_line) if intersect: if s1 == 1 or s2 == 1: seg_left = [p1 if s1 ==1 else intersect, p2 if s2 ==1 else intersect] current_left.append(seg_left) if s1 == -1 or s2 == -1: seg_right = [p1 if s1 ==-1 else intersect, p2 if s2 ==-1 else intersect] current_right.append(seg_right) # 此处可添加逻辑处理多段线断开的情况 else: # 圆弧元素 (cx, cy), r, start_angle, end_angle = elem start_pt = (cx + r*math.cos(math.radians(start_angle)), cy + r*math.sin(math.radians(start_angle))) end_pt = (cx + r*math.cos(math.radians(end_angle)), cy + r*math.sin(math.radians(end_angle))) s_start = point_side(start_pt, split_line) s_end = point_side(end_pt, split_line) if s_start == s_end: if s_start ==1: current_left.append(elem) elif s_start ==-1: current_right.append(elem) else: intersects = get_arc_split_intersections(elem, split_line) for idx, intersect in enumerate(intersects): intersect_angle = math.degrees(math.atan2(intersect[1]-cy, intersect[0]-cx)) if intersect_angle <0: intersect_angle +=360 # 拆分圆弧 arc1 = [(cx, cy), r, start_angle, intersect_angle] arc2 = [(cx, cy), r, intersect_angle, end_angle] # 判断拆分后圆弧的所属侧 mid_angle1 = (start_angle + intersect_angle)/2 mid_pt1 = (cx + r*math.cos(math.radians(mid_angle1)), cy + r*math.sin(math.radians(mid_angle1))) if point_side(mid_pt1, split_line) ==1: current_left.append(arc1) else: current_right.append(arc1) mid_angle2 = (intersect_angle + end_angle)/2 mid_pt2 = (cx + r*math.cos(math.radians(mid_angle2)), cy + r*math.sin(math.radians(mid_angle2))) if point_side(mid_pt2, split_line) ==1: current_left.append(arc2) else: current_right.append(arc2) # 整理最终多段线列表 left_polylines = [current_left] if current_left else [] right_polylines = [current_right] if current_right else [] # 实际场景需补充处理拆分后产生的独立多段线 return left_polylines, right_polylines
关键注意事项
- 处理浮点数精度:设置合理的误差阈值(如
1e-9),避免因计算误差导致的逻辑错误 - 圆弧方向处理:拆分后的圆弧需保持原有的顺时针/逆时针方向(通过
theta的正负性维持) - 多段线重组:需额外跟踪片段的首尾连接关系,确保拆分后的多段线连续完整
内容的提问来源于stack exchange,提问作者mmj
相关产品推荐
相关产品推荐

