1. 轨道几何不平顺数据文件结构解析与字段映射
原始文件长什么样:先看清“行结构”再谈字段
轨道几何检测原始数据通常以纯文本逐行记录的形式落盘,一行对应一个采样点。不同检测系统(综合检测车、轨道检查仪、便携式添乘仪)导出的文件在分隔符、表头、单位上差异很大,但核心信息是一致的:里程 + 各项几何参数。解析的第一步不是写映射代码,而是确认三件事:
- 分隔符:逗号、制表符、空格对齐或定宽列;
- 是否有表头:有表头可直接按名取列,无表头必须按列序约定;
- 单位与量纲:轨距常为 mm,水平/高低/轨向常为 mm,里程常为 m 或 km,部分系统用 0.1 mm 或 0.01 mm 计数。
下面是一份典型导出片段(示例数据,制表符分隔,含表头,里程单位 m,几何量单位 mm):
MILEAGE GAUGE LEVEL ALIGN_L ALIGN_R PROFILE_L PROFILE_R
K120+000.0 1435.2 1.8 -0.6 0.4 2.1 -1.3
K120+000.5 1435.5 2.1 -0.5 0.5 2.4 -1.1
K120+001.0 1436.1 2.6 -0.3 0.7 2.9 -0.8
K120+001.5 1434.8 1.5 -0.9 0.2 1.7 -1.6
注意两个“坑”:里程写成 K120+000.0 这种公里标+米的复合格式,不能直接当浮点数用;轨向、高低按左右股分列存储,而标准模型通常要求“单值 + 股别”或“左右分别建模”。字段映射的本质,就是把这些异构的存储形态统一到一套标准数据模型上。
字段映射关系:从原始列到标准模型
本例采用的标准数据模型字段为:mileage_m(里程,m,浮点)、gauge_mm(轨距,mm)、level_mm(水平,mm,带符号)、alignment_mm(轨向,mm,带符号)、profile_mm(高低,mm,带符号)、side(股别:L/R)。映射规则如下:
| 原始列 | 标准字段 | 转换规则 | 单位处理 |
|---|---|---|---|
| MILEAGE | mileage_m | 解析 K{km}+{m} → km×1000 + m | m |
| GAUGE | gauge_mm | 直接取值 | mm |
| LEVEL | level_mm | 保留符号 | mm |
| ALIGN_L / ALIGN_R | alignment_mm + side | 拆为两行,side=L/R | mm |
| PROFILE_L / PROFILE_R | profile_mm + side | 拆为两行,side=L/R | mm |
用 Python 完成解析与映射
下面代码自包含、可直接运行:内置示例数据写入临时文件,再按“读行 → 解析里程 → 单位归一 → 左右股拆分 → 输出结构化表”的顺序完成映射。里程解析用正则提取公里与米,单位系数集中在一个字典里,便于不同检测系统复用。
import re
import csv
import io
RAW = """MILEAGE\tGAUGE\tLEVEL\tALIGN_L\tALIGN_R\tPROFILE_L\tPROFILE_R
K120+000.0\t1435.2\t1.8\t-0.6\t0.4\t2.1\t-1.3
K120+000.5\t1435.5\t2.1\t-0.5\t0.5\t2.4\t-1.1
K120+001.0\t1436.1\t2.6\t-0.3\t0.7\t2.9\t-0.8
K120+001.5\t1434.8\t1.5\t-0.9\t0.2\t1.7\t-1.6"""
# 单位系数:原始值 × 系数 = mm(示例文件已是 mm,系数为 1.0)
UNIT_SCALE = {"GAUGE": 1.0, "LEVEL": 1.0, "ALIGN": 1.0, "PROFILE": 1.0}
MILEAGE_RE = re.compile(r"^K(\d+)\+(\d+(?:\.\d+)?)$")
def parse_mileage(text):
"""K120+000.5 -> 120000.5 (m)"""
m = MILEAGE_RE.match(text.strip())
if not m:
raise ValueError(f"无法解析里程: {text!r}")
km, meters = m.group(1), m.group(2)
return int(km) * 1000.0 + float(meters)
def parse_file(text):
reader = csv.DictReader(io.StringIO(text), delimiter="\t")
rows = []
for raw in reader:
mileage = parse_mileage(raw["MILEAGE"])
gauge = float(raw["GAUGE"]) * UNIT_SCALE["GAUGE"]
level = float(raw["LEVEL"]) * UNIT_SCALE["LEVEL"]
for side in ("L", "R"):
rows.append({
"mileage_m": mileage,
"gauge_mm": gauge,
"level_mm": level,
"alignment_mm": float(raw[f"ALIGN_{side}"]) * UNIT_SCALE["ALIGN"],
"profile_mm": float(raw[f"PROFILE_{side}"]) * UNIT_SCALE["PROFILE"],
"side": side,
})
return rows
rows = parse_file(RAW)
print(f"原始数据行数: 4, 映射后行数: {len(rows)}")
for r in rows[:4]:
print(r)
运行后应看到:原始 4 行数据被展开为 8 行(每点左右股各一行),里程由 K120+000.0 变为 120000.0,字段名与单位全部对齐标准模型。示例输出(由给定输入直接推出):
原始数据行数: 4, 映射后行数: 8
{'mileage_m': 120000.0, 'gauge_mm': 1435.2, 'level_mm': 1.8, 'alignment_mm': -0.6, 'profile_mm': 2.1, 'side': 'L'}
{'mileage_m': 120000.0, 'gauge_mm': 1435.2, 'level_mm': 1.8, 'alignment_mm': 0.4, 'profile_mm': -1.3, 'side': 'R'}
{'mileage_m': 120000.5, 'gauge_mm': 1435.5, 'level_mm': 2.1, 'alignment_mm': -0.5, 'profile_mm': 2.4, 'side': 'L'}
{'mileage_m': 120000.5, 'gauge_mm': 1435.5, 'level_mm': 2.1, 'alignment_mm': 0.5, 'profile_mm': -1.1, 'side': 'R'}
映射正确性的三项校验
字段映射最容易出错的地方不是代码,而是“看起来对、其实错”。建议在解析后立即做三项检查:
- 行数守恒:映射后行数 = 原始行数 × 2(左右股拆分时),若不等说明有列缺失或表头不匹配。
- 里程单调递增:同一股别内
mileage_m应严格递增,出现回退说明里程解析或排序有问题。 - 量纲合理性:轨距应在 1400–1470 mm 附近,水平/高低/轨向绝对值一般不超过 30 mm;若出现 14352 这类值,多半是原始单位为 0.1 mm 而系数未设。
若原始文件无表头,用
csv.reader 按列序读取,并把列序约定写成常量(如 COL_MILEAGE=0, COL_GAUGE=1 ...),避免魔法数字散落在代码里。不要假设所有检测系统的里程都是“公里+米”格式。有的系统直接给累计米数(如 120000.5),有的给“120+000.5”。解析前先抽样 3–5 行确认格式,再决定正则或分支逻辑,否则会在整条线路数据上静默出错。