【编程实践】隧道断面的超欠挖面积及体积计算(断面法)

隧道断面超欠挖概念

隧道断面超欠挖是指在隧道施工过程中,实际开挖断面与设计开挖断面之间的偏差。

  1. 定义
    超挖:实际开挖断面大于设计开挖断面。
    欠挖:实际开挖断面小于设计开挖断面

在这里插入图片描述
在这里插入图片描述

  1. 影响及控制意义
    影响:
    超挖会增加工程成本,可能导致围岩失稳,影响隧道结构的稳定性。
    欠挖会降低隧道净空,影响后续衬砌施工和运营安全。
    控制意义:
    严格控制超欠挖可以保证隧道施工质量,减少安全隐患,提高工程经济效益。

施工规范和标准

隧道超欠挖的控制依据主要来源于相关施工规范和技术标准

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
条文摘要:
第11.2.6条规定:
隧道开挖断面允许超欠挖限值应符合设计要求,未作规定时应满足:

  • 拱部超挖不得超过设计断面外轮廓100 mm;
  • 侧壁不得超过150 mm;
  • 欠挖不得影响结构施工和净空要求。
    在这里插入图片描述
    条文摘要(第9.3节):
    超挖控制限值(单位:mm):
  • 拱部 ≤ 100 mm
  • 侧墙 ≤ 150 mm
  • 仰拱 ≤ 100 mm
  • 欠挖应满足净空和衬砌施工要求。
  • 且强调“不得影响支护结构的安全性与施工工艺”。

(上述标准,于2025年5月18日查询,属于现行规范。)

《公路隧道施工技术规范》(JTG F60-2009,已废止,新规范为JTG/T3660-2020)中对超欠挖规定:应严格控制欠挖。拱脚和墙脚以上1m内范围严禁欠挖。应尽量减少超挖,不同围岩地质条件下的允许超挖值规定如下表(F69-2009版,目前没找到最新标准的文件,找到后进行更新):
在这里插入图片描述
在这里插入图片描述

超欠挖计算方法

常用体积计算方法

  1. 断面法(最常用):

原理:在两个相邻断面间,用平均面积乘以断面间距

公式: V = Σ [ ( A 1 + A 2 ) / 2 × L ] V = Σ[(A₁ + A₂)/2 × L] V=Σ[(A1+A2)/2×L]

A 1 A_1 A1 A 2 A_2 A2: 相邻断面的超欠挖面积; L L L:断面间距

优点:计算简单,适用于规则间距断面

缺点:精度受断面密度影响

  1. 三角网法:

原理:构建设计面和实际开挖面的三角网,计算两曲面间体积

优点:精度高

缺点:计算复杂,需要处理大量三角面片

  1. 格网法:

原理:将区域划分为格网,计算每个格网柱体的体积差

优点:适合规则区域

缺点:精度受格网大小影响

  1. 点云直接计算法:

原理:计算每个点到设计面的距离,积分得到体积

优点:直接利用原始数据

缺点:计算量大


def calculate_overbreak_underbreak(scan_pcd, section_pcd, route_pcd, section_frames, output_dir="output/OUbreak_pic"):
    """
    优化后的超欠挖计算函数,解决点云包围和空点云问题
    """
    import os
    import matplotlib.pyplot as plt
    from shapely.geometry import Polygon, MultiPolygon
    from shapely.ops import clip_by_rect, unary_union
    from scipy.spatial import Delaunay
    os.makedirs(output_dir, exist_ok=True)
    route_points = np.asarray(route_pcd.points)
    section_points = np.asarray(section_pcd.points)
    scan_points = np.asarray(scan_pcd.points)
    total_sections = len(route_points)
    points_per_section = len(section_points) // total_sections
    section_areas = []
    for i in range(total_sections):
        try:
            frame = section_frames[i]
            start_idx = i * points_per_section
            end_idx = (i + 1) * points_per_section
            design_points = section_points[start_idx:end_idx]
            # 投影扫描点到断面,并增加水平过滤
            distances = np.dot(scan_points - frame['point_base'], frame['dir_vec'])
            mask = np.abs(distances) < 0.2  # 20cm阈值
            nearby_points = scan_points[mask] 
            # 检查是否有扫描点
            if len(nearby_points) == 0:
                print(f"警告: 断面 {i} (里程 {route_points[i,0]:.3f}m) 无点云数据,跳过计算")
                section_areas.append((0.0, 0.0))
                continue 
            # 转换到2D局部坐标系
            local_design = []
            for point in design_points:
                vec = point - frame['point_base']
                x = np.dot(vec, frame['x_axis'])
                y = np.dot(vec, frame['y_axis'])
                local_design.append([x, y])
            
            local_scan = []
            for point in nearby_points:
                vec = point - frame['point_base']
                x = np.dot(vec, frame['x_axis'])
                y = np.dot(vec, frame['y_axis'])
                local_scan.append([x, y])
            
            # 创建多边形
            design_poly = Polygon(local_design).convex_hull
            scan_poly = Polygon(local_scan).convex_hull if len(local_scan) >= 3 else Polygon()
            
            # 计算有效扫描区域(避免完全包围误判)
            if not scan_poly.is_empty:
                # 计算扫描点与设计面的交集
                if design_poly.within(scan_poly):
                    # 实测轮廓点完全包围设计断面,无欠挖
                    overbreak = scan_poly.difference(design_poly)
                    underbreak = Polygon()
                    valid_scan = design_poly
                else:
                    valid_scan = scan_poly.intersection(design_poly)
                    overbreak = scan_poly.difference(design_poly)
                    underbreak = design_poly.difference(valid_scan) if not valid_scan.is_empty else design_poly
            else:
                overbreak = Polygon()
                underbreak = design_poly
                valid_scan = Polygon()
            
            # 计算面积
            def get_area(geom):
                if geom.is_empty:
                    return 0.0
                if isinstance(geom, (Polygon, MultiPolygon)):
                    return geom.area
                return 0.0
            
            overbreak_area = get_area(overbreak)
            underbreak_area = get_area(underbreak)
            section_areas.append((overbreak_area, underbreak_area))
            
            # 可视化===不同绘图顺序会得到不同可视结果
            plt.figure(figsize=(10, 8))
            
            # 先绘制超挖区域
            if not overbreak.is_empty:
                if isinstance(overbreak, Polygon):
                    plt.fill(*overbreak.exterior.xy, 'r', alpha=0.3, label='Overbreak')
                elif isinstance(overbreak, MultiPolygon):
                    for poly in overbreak.geoms:
                        plt.fill(*poly.exterior.xy, 'r', alpha=0.3)
            
            # 绘制扫描点云的有效轮廓
            if not scan_poly.is_empty and hasattr(scan_poly, 'exterior'):
                plt.plot(*scan_poly.exterior.xy, 'b--', label='Scan Area')
                if hasattr(valid_scan, 'exterior'):
                    plt.plot(*valid_scan.exterior.xy, 'b-', label='Valid Scan')
            # # 绘制有效扫描区域
            # if not valid_scan.is_empty and hasattr(valid_scan, 'exterior'):
            #     plt.plot(*valid_scan.exterior.xy, 'b-', label='Valid Scan')
            # 绘制设计轮廓(最上层)
            if hasattr(design_poly, 'exterior'):
                plt.fill(*design_poly.exterior.xy, facecolor='white', edgecolor='none', zorder=1)
                plt.plot(*design_poly.exterior.xy, 'g-', label='Design Profile')

            # 绘制欠挖区域
            if not underbreak.is_empty:
                if isinstance(underbreak, Polygon):
                    plt.fill(*underbreak.exterior.xy, 'y', alpha=0.3, label='Underbreak')
                elif isinstance(underbreak, MultiPolygon):
                    for poly in underbreak.geoms:
                        plt.fill(*poly.exterior.xy, 'y', alpha=0.3)

            plt.title(f"Section at Mileage {route_points[i,0]:.2f}m\n"
                     f"Overbreak: {overbreak_area:.4f} m² | Underbreak: {underbreak_area:.4f} m²")
            plt.legend()
            plt.grid(True)
            plt.axis('equal')
            plt.savefig(os.path.join(output_dir, f"section_{i:04d}.png"))
            plt.close() 
        except Exception as e:
            print(f"处理断面 {i} 时出错: {str(e)}")
            section_areas.append((0.0, 0.0))
    return np.array(section_areas)

以断面法进行编程实现

计算超欠挖面积

  1. 输入数据
    隧道点云
    设计断面
    线路中心线,用于区分每一个断面的位置
    每个断面对应的局部坐标系
  2. 每一断面的处理流程
    对于每个断面i
    a. 提取该断面的设计断面点
design_points = section_points[start_idx:end_idx]

b. 找到靠近断面平面(20cm以内)的扫描点

distances = np.dot(scan_points - frame['point_base'], frame['dir_vec'])
mask = np.abs(distances) < 0.2
nearby_points = scan_points[mask]

c. 将三维点转换为局部二维平面坐标系(断面坐标系)
对每个点进行投影:
x = ( P − P 0 ) ⋅ X a x i s y = ( P − P 0 ) ⋅ Y a x i s x = (P - P_0) \cdot X_{axis} \\ y = (P - P_0) \cdot Y_{axis} x=(PP0)Xaxisy=(PP0)Yaxis
其中, P P P是点, P 0 P_0 P0是断面参考点, X a x i s X_{axis} Xaxis Y a x i s Y_{axis} Yaxis是局部平面X、Y周单位向量。
d. 构建2D多边形

  • design_poly: 由设计断面点形成的设计多边形
  • scan_poly:由扫描点形成的扫描轮廓多边形

e. 计算超欠挖区域(用到了多边形差集、交集操作,来自Shapely库)

  • 如果scan_poly完全包围design_poly:说明无欠挖,只有超挖
overbreak = scan_poly.difference(design_poly)
underbreak = Polygon()
  • 否则:计算
    • 有效扫描区域(交集):valid_scan = scan_poly ∩ design_poly
    • 超挖:scan_poly - design_poly
    • 欠挖:design_poly - valid_scan

f. 计算面积
对多边形或复合多边形:
A = A r e a ( P o l y g o n ) A = Area(Polygon) A=Area(Polygon)
g. 绘图并保存结果

  • 图像中标注:
    • 超挖区域
    • 欠挖区域
    • 扫描轮廓
    • 设计轮廓
  • 在图中写明面积信息

计算超欠挖体积

  1. 输入数据
section_areas: n × 2的数组 # 每一行表示一个断面的(超挖面积,欠挖面积)
mileage: n 长度的数组 # 每个断面的中心里程
  1. 计算逻辑
    在每两个相邻断面之间 i i i i + 1 i+1 i+1,用平均面积法估算体积:
    原理:
    对区段长度 L i = m i l e a g e i + 1 − m i l e a g e i L_i = mileage_{i+1}- mileage_{i} Li=mileagei+1mileagei,其估算体积为:
    V i = A i + A i + 1 2 ⋅ L i V_i = \frac{A_i + A_{i+1}}{2} \cdot L_i Vi=2Ai+Ai+1Li
avg_overbreak = (A_i_over + A_next_over)/2
avg_underbreak = (A_i_under + A_next_under)/2
def calculate_volume(section_areas, mileage):
    """
    基于断面面积计算超欠挖体积
    :param section_areas: 每个断面的超欠挖面积数组 (n_sections x 2)
    :param mileage: 路线里程数组
    :return: (total_overbreak_vol, total_underbreak_vol)
    """
    total_overbreak = 0.0
    total_underbreak = 0.0
    
    for i in range(len(section_areas) - 1):
        L = mileage[i+1] - mileage[i]  # 断面间距
        
        # 平均面积法
        avg_overbreak = (section_areas[i,0] + section_areas[i+1,0]) / 2
        avg_underbreak = (section_areas[i,1] + section_areas[i+1,1]) / 2
        
        total_overbreak += avg_overbreak * L
        total_underbreak += avg_underbreak * L
    
    return total_overbreak, total_underbreak

结果可视化

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值