用Python自动化道路实验报告:断面交通量调查到Word生成
简介高校地理与规划学院《城市交通与道路系统规划》课程实验报告适合城乡规划、交通工程等专业学生和课程作业参考。报告完整涵盖“道路网方案设计”与“平面图绘制”两个实验项目详细记录了AutoCAD与湘源控规的操作流程包括新建图纸、图层分类、道路间距与宽度设置、单线转双线、交叉口导圆角、规划范围线绘制等关键步骤并保留实验目的、过程、结论与问题思考能帮助读者建立从整体路网布局到细部修改的完整工作思路。压缩包为1个doc文档大小约359KB内容结构规整并包含原始实验守则、实验记录表格及教师评语等信息便于对照参考。目前已有54人浏览学习适合正在完成同类道路规划实验、希望规范图纸表达或补充实验报告细节的读者查阅借鉴。1. 道路实验报告的数据坑从调查表就埋下了一场两小时的断面交通量调查现场只用记四列数时间段、方向、车型、辆数。可大多数小组回到机房才发现真正费时间的不是数车而是把本子上的正字整理成能写进道路实验报告的那几张表——15分钟流量要合成小时流量车型要折算成标准小客车当量高峰要取准图表还要插进Word文档里。这中间任何一个环节靠手工做都容易出账目对不上的问题。这篇博文把城市交通与道路系统规划里最常见的道路实验——断面交通量调查——从原始记录到成稿报告的处理链路完整走一遍字段设计、pcu折算、高峰小时与PHF计算、流量图绘制、Word报告生成最后补一套自检方法照着做能省下大半晚熬夜。2. 道路实验报告的数据模型先从记录表设计下手2.1 现场记录为什么用四列窄表每个路口数车的小组都会遇到一个经典问题记录表到底是横着排还是竖着排。很多人习惯在Excel里把方向做成列、车型也做成列时间往下走做一张宽表。这种表人看着舒服回来做分析时用Pandas做透视却极其别扭而且一旦某个方向漏记了5分钟宽表里要补的空格特别多最后连哪个空格是“没车”哪个是“没记”都分不清。我一般建议现场用手机表单按“一条记录一行”的方式记录每5分钟一行字段只保留四个interval_start起始时间ISO格式、direction方向码、vehicle_type车型码、count辆数。这样一份原始记录长这样interval_startdirectionvehicle_typecount2024-05-14 07:00NPC122024-05-14 07:00NHC32024-05-14 07:00SPC92024-05-14 07:05NPC152024-05-14 07:05SHC2这里N和S分别代表北行、南行PC是小型客车、HC是大型客车或货车。两个细节要注意时间字段宁可写成2024-05-14 07:00也不要写成7:00否则Excel会在你不知情时转成日期时间类型导入Pandas后还要再纠正一次格式方向码和车型码用统一的大写字母简写别写“南向北”因为不同人写的“南→北”和“南-北”最后会让groupby多出一组来。2.2 车型折算系数做成配置别写死在公式里交通量调查里所有车型都要折算成标准小客车当量pcu这是道路实验报告里最基础的一步。不同课程和不同规范给的折算系数略有差异常见做法是先把车型分成三到四类再给每类一个系数。代码里我习惯把系数放到一个字典里PCU { PC: 1.0, # 小型客车 HC: 2.0, # 大型客车/货车 MC: 0.5, # 摩托车 }把系数做成字典而不是写死在公式里是因为换一套规范、换一个系数表时只需要改这一处后面的聚合、绘图、报告文字全部自动跟随。以前见过有同学把系数直接乘在计算公式里结果换系数时改了十多个地方还漏了末班时段的那几行最后高峰流量差了几十辆老师一眼就看出来。2.3 高峰小时与PHF先讲清口径PHF高峰小时系数的计算口径是高峰小时流量除以4倍的高峰小时内最大15分钟流量。这个指标反映流量在一个小时内的集中程度。流量均匀分布在4个15分钟里时PHF接近1都挤在一个15分钟里时PHF会掉到0.6甚至更低。计算时最容易出问题的是“高峰小时”本身怎么取。先按15分钟粒度把全天流量排成连续断面然后取任意连续4个断面求和全天最大的那个四连段就是高峰小时。不同老师要求的口径可能略有差异有些做法直接用全观测时段最大15分钟为中心向两边扩一个小时结果会稍有差别。写报告前先把这个口径确认清楚否则PHF对不上手算值返工很麻烦。3. 用Pandas把15分钟断面流量算成高峰小时指标3.1 读取CSV并做基础清洗把现场记录存成CSV后第一步是读进来做类型清洗。直接用pd.read_csv读的时间列通常是字符串需要转成datetime64类型否则后面按时间排序、切片、rolling都会出问题。import pandas as pd df pd.read_csv(traffic_survey.csv, encodingutf-8-sig) df[interval_start] pd.to_datetime(df[interval_start]) # 折算标准小客车当量map找不到的车型会变成NaN df[pcu] df[vehicle_type].map(PCU) * df[count]这里encodingutf-8-sig是专门处理中文环境的Excel另存的CSV经常是带BOM的UTF-8不用这个参数的话第一列列名会多出一个\ufeff。df[vehicle_type].map(PCU)按字典做映射如果CSV里混入了字典里没有的车型代码映射结果是NaN整行pcu都会变成NaN。所以下一步一定要先用df[vehicle_type].value_counts()核对一遍枚举值确认所有车型代码都在字典里再继续算。3.2 按时间段和方向聚合成断面流量表清洗完就把原始记录透视成“每一行是一个15分钟断面、每一列是一个方向”的宽表这才是后面计算高峰小时的基础表结构。agg ( df.groupby([interval_start, direction])[pcu] .sum() .unstack(fill_value0) .sort_index() )unstack(fill_value0)的作用是把direction这一列转成列索引某个方向没有记录的时间段自动补0。这里有个隐患补0会掩盖“漏记”和“真的没车”这两种情况。第5章会专门讲缺失时段检查在做聚合之前最好先确认数据没有整段缺失否则补出来的0会直接影响高峰小时识别的结果。3.3 高峰小时识别与PHF计算有了15分钟断面表高峰小时就是对所有连续4个断面求和取最大值。Pandas的rolling(4)正好做这个事但坑在rolling的窗口标签指向的是窗口最后一个时间点。hourly agg.rolling(4, min_periods4).sum() hourly_total hourly.sum(axis1) # 窗口标签是高峰小时结束时刻要往前推45分钟才是起点 peak_end hourly_total.idxmax() peak_start peak_end - pd.Timedelta(minutes45) peak_window agg.loc[peak_start:peak_end] peak_hour_flow peak_window.sum().sum() peak_15 peak_window.sum(axis1).max() phf peak_hour_flow / (4 * peak_15) print(高峰小时, peak_start, -, peak_end) print(高峰小时流量pcu, round(peak_hour_flow, 1)) print(PHF, round(phf, 3))min_periods4保证只有凑满4个15分钟断面的窗口才参与计算避免早晨刚开始调查时的不足4个断面被当成短窗口。idxmax()取到的下标是滚动窗口最后一条记录对应的时刻所以实际高峰小时起点要往前推45分钟。我第一次跑这个脚本时直接打印peak_end写进报告把高峰小时写成了9:00到10:00实际是8:15到9:15这种错误手算很难发现代码里按注释处理就稳了。3.4 V/C比与服务水平的对应高峰小时流量拿到后下一步是和设计通行能力做对比算V/C比再对照服务水平分级给结论。通行能力参数每个路段的设定值不一样代码里做成变量报告里说明取值依据即可。capacity 1800 # 单向设计通行能力按实验任务书取值 v_over_c peak_hour_flow / capacity LOS_TABLE [ (0.35, A), (0.55, B), (0.75, C), (0.95, D), (1.00, E), ] def los_of(vc): for threshold, grade in LOS_TABLE: if vc threshold: return grade return F print(V/C, round(v_over_c, 2)) print(服务水平, los_of(v_over_c))服务水平分级阈值不同规范之间有差别这里给的是城市道路常用的参考区间写报告时以课程指定的规范为准。整个第3章的核心思路是把所有中间计算结果都存成变量而不是算一步打印一步再手动抄进报告这样后面画图、生成Word时可以直接引用同一批数值保证报告里的图和文字出自同一份计算。4. 出图与生成Word版道路实验报告4.1 用matplotlib画流量时变曲线和车型占比报告里最常放的两张图一是全天流量时变曲线按方向分两条线标注高峰小时区间二是车型占比饼图用来描述调查断面的交通组成。先画第一张import matplotlib matplotlib.use(Agg) # 无界面环境也稳定 import matplotlib.pyplot as plt plt.rcParams[font.sans-serif] [SimHei, Noto Sans CJK SC] plt.rcParams[axes.unicode_minus] False fig, ax plt.subplots(figsize(10, 4.5)) for col in agg.columns: ax.plot(agg.index, agg[col], labelf方向{col}) ax.axvspan(peak_start, peak_end, colororange, alpha0.15, label高峰小时) ax.set_xlabel(时间) ax.set_ylabel(15分钟流量pcu) ax.set_title(断面交通量时变曲线) ax.legend() fig.tight_layout() fig.savefig(flow_curve.png, dpi200)axvspan用来把高峰小时区间标成阴影读者一眼就能看到高峰时段在哪。中文字体这里单独设了sans-serif列表Windows下用SimHeiLinux下会回退到Noto Sans CJK如果两个都没有图里中文会变成方块这是matplotlib最常见的坑。dpi200是给Word插图留足清晰度直接截图放进去的图放大后发虚评审老师不喜欢。车型占比图用agg的前置数据来做按车型汇总整个观测时段的pcu合计pcu_by_type df.groupby(vehicle_type)[pcu].sum() fig2, ax2 plt.subplots(figsize(5, 5)) ax2.pie(pcu_by_type.values, labelspcu_by_type.index, autopct%.1f%%) ax2.set_title(观测时段车型构成按pcu) fig2.tight_layout() fig2.savefig(type_share.png, dpi200)饼图不适合有太多分类时使用这里车型只有三类所以没问题。如果分类超过五类建议改成横向条形图占比小的类别堆在一起饼图根本看不清楚。4.2 用python-docx把计算结果写进道路实验报告图片生成后接着用python-docx创建Word文档。先搭报告的骨架标题、调查基本信息、计算表格、图片、结论。add_heading的level参数0对应Word的“标题”样式1对应一级标题这里用0让报告标题独立成行。from docx import Document from docx.shared import Inches doc Document() doc.add_heading(城市交通与道路系统规划——道路实验报告, 0) doc.add_paragraph( 调查地点人民路-建设路交叉口 调查日期2024-05-14 调查时段07:00-09:00 ) doc.add_heading(1. 调查数据汇总, level1) hdr doc.add_table(rows1, cols4) hdr.style Table Grid hdr.rows[0].cells[0].text 指标 hdr.rows[0].cells[1].text 数值 hdr.rows[0].cells[2].text 指标 hdr.rows[0].cells[3].text 数值 rows [ (高峰时段, f{peak_start:%H:%M}-{peak_end:%H:%M}, 高峰小时流量, f{peak_hour_flow:.0f} pcu), (PHF, f{phf:.3f}, V/C, f{v_over_c:.2f}), (服务水平, los_of(v_over_c), 设计通行能力, f{capacity} pcu/h), ] for r in rows: cells hdr.add_row().cells for i, v in enumerate(r): cells[i].text v doc.add_heading(2. 流量时变曲线, level1) doc.add_picture(flow_curve.png, widthInches(5.8)) doc.add_heading(3. 车型构成分析, level1) doc.add_picture(type_share.png, widthInches(4.8)) doc.save(道路实验报告.docx)doc.add_table创建表格后要先add_row再给各单元格赋文本新行默认没有数据直接row.cells[0].text取值会报索引错误。doc.add_picture插入图片时按Inches控制宽度两张图一张宽一张窄排出来页面会更透气不会所有内容都顶到页边距。表格样式用Table Grid最保险有的模板环境下Light Grid Accent 1这类样式名不存在会直接抛KeyError。4.3 报告模板化的变量管理实验报告每次要改的其实就是调查地点、日期、组员、班级、调查时段这几个字段。每次生成前手动改代码里的字符串既容易漏改又容易在复制粘贴时把上一组的调查地点留在报告里。我的做法是把这些信息集中到一个字典里meta { course: 城市交通与道路系统规划, title: 道路实验报告, site: 人民路-建设路交叉口, date: 2024-05-14, period: 07:00-09:00, team: 第三组, members: 张三、李四、王五, }然后所有写入文档的段落用f-string从meta取值比如doc.add_paragraph(f调查地点{meta[site]} 调查日期{meta[date]})。这样每次换数据只在最上面改一处文档结构代码完全不用动。更进一步可以把meta存成JSON文件一组一份配置批量生成多个小组的报告时直接循环读文件就行。模板化之后的道路实验报告生成速度从手动整理两小时缩短到跑一次脚本几十秒剩下的时间全用来核数据。5. 数据自检缺时段、极值与双向不平衡5.1 缺失时段检测与插补策略agg表虽然用fill_value0补了缺失但如果某个15分钟整段没有记录补0会让高峰小时计算失真。生成报告前先跑一段检测expected pd.date_range( start2024-05-14 07:00, end2024-05-14 09:00, freq15min, ) missing expected.difference(agg.index) if len(missing) 0: print(缺失时段, missing)发现缺失后不要直接删先用原始记录本核对是“没车”还是“没记”。确认漏记的可以用前后断面线性插补agg agg.reindex(expected).interpolate(limit2)reindex先把缺失的行补成NaNinterpolate用线性插值填上limit2限制最多连补两个断面。如果连续缺失超过半小时插值就不可信了这时候宁可删掉整个高峰小时识别的结果在报告里注明该时段数据无效也不要硬补。5.2 双向不平衡与极值检查调查路段双向流量一般不会差太多。把两个方向的流量分别求和算不平衡度超过30%就得回去查原因total_n agg[N].sum() total_s agg[S].sum() imbalance abs(total_n - total_s) / (total_n total_s) print(f双向不平衡度{imbalance:.2%})另外还要检查单断面极值。某个15分钟流量突然是相邻断面的三四倍大概率是记录错误比如把两台车的数字写成了20。用均值加减三倍标准差粗筛mu agg[N].mean() sd agg[N].std() outliers agg[agg[N] mu 3 * sd] print(outliers)筛出来的异常断面逐个回看原始记录确认是真实突发流量比如公交车场出车就保留查不到依据的就标记并修正。别直接删报告中要保留数据修正说明这也是实验报告评分里“数据处理规范”这一项。5.3 结论表述与计算口径对齐报告最后一节写结论时只写本次调查能支撑的数据事实高峰时段出现在什么区间、PHF是多少、V/C是多少、对应哪一级服务水平。不要因为V/C超过0.9就写“该路段严重拥堵”调查数据里没有车速和延误信息拥堵程度不能直接从流量推。另外所有数值要和正文表格保持一致我的习惯是把第3章算出的各项指标统一打印一遍把运行结果和Word里生成的表格逐项比对确认后再保存文档。先跑一遍检查脚本再写结论能减少大量因口径不一致导致的返工。本文还有配套的精品资源点击获取