80行Python:校车火车碰撞预警原型
场景:每天重复的盲区风险
比利时Buggenhout的悲剧不是孤例。校车每天在固定路线行驶,经过无数平交道口。大多数道口有栏杆和信号灯,但真正致命的场景往往发生在无防护道口、临时调车、或人为失误。事故报道中提到“emergency services take images with their smartphones”,说明现场缺乏实时位置上报——如果校车和火车的位置能被系统自动监测,或许就能争取到那一分钟的刹车时间。
自动化后的效果对比
- 之前:依赖司机目视、道口信号、调度电话,任何一环断裂就会出事。
- 之后:用低成本GPS模块(定位精度3-5米)+ 火车时刻表API(或实时位置推送),每5秒计算一次校车与最近火车的距离,当距离小于预设阈值(比如500米)且相对速度超过安全值,立即向司机手机、调度中心推送警报。
实际效果不是取代人,而是在人反应之前给第二次机会。
工具组合和流程图
text
1
2
3
4
5
6
[校车GPS] → MQTT/HTTP → [云服务器]
[火车实时位置]或[时刻表推算] → [云服务器]
↓
碰撞检测引擎(Python脚本)
↓
触发警报 → 推送通知(Pushbullet/Webhook)
- 校车端:树莓派Zero W + NEO-6M GPS模块,每秒获取经纬度。
- 火车数据:如果比利时铁路有公开API,直接拉取;若无,用固定时刻表 + 线性插值估算位置(注意误差)。
- 云服务器:最简单的方案是本地笔记本+定时任务,生产环境用轻量云(如华为云、AWS Lightsail)。
- 推送:Pushbullet免费版每天100次推送,或企业微信机器人Webhook。
关键节点配置(完整代码)
以下是一个碰撞检测核心脚本,可直接运行(需要伪造火车位置演示):
python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
import math
import time
from datetime import datetime
# ---------- 工具函数 ----------
def haversine(lat1, lon1, lat2, lon2):
"""返回两点间距离(米)"""
R = 6371000
phi1, phi2 = math.radians(lat1), math.radians(lat2)
dphi = math.radians(lat2 - lat1)
dlambda = math.radians(lon2 - lon1)
a = math.sin(dphi/2)**2 + math.cos(phi1)*math.cos(phi2)*math.sin(dlambda/2)**2
return 2 * R * math.atan2(math.sqrt(a), math.sqrt(1-a))
# ---------- 模拟数据 ----------
buses = [
{"id": "bus1", "lat": 51.015, "lon": 4.200, "speed": 10, "direction": 0}
]
# 假设火车从东向西行驶,位置根据时刻表推算
trains = [
{"id": "train1", "lat": 51.012, "lon": 4.210, "speed": 25, "direction": 270}
]
WARNING_DISTANCE = 500 # 米
CRITICAL_DISTANCE = 200
def check_collision(bus, train):
dist = haversine(bus["lat"], bus["lon"], train["lat"], train["lon"])
# 简单速度投影:判断两者是否靠近(取相对速度沿连线方向的分量)
# 这里简化:如果距离在阈值内且相对速度为正(互相靠近)则报警
if dist < CRITICAL_DISTANCE:
return "CRITICAL", dist
elif dist < WARNING_DISTANCE:
# 估算1秒后的距离(假设直线运动)
new_bus_lat = bus["lat"] + bus["speed"] * math.cos(math.radians(bus["direction"])) * (1/111320)
new_bus_lon = bus["lon"] + bus["speed"] * math.sin(math.radians(bus["direction"])) * (1/(111320*math.cos(math.radians(bus["lat"]))))
new_train_lat = train["lat"] + train["speed"] * math.cos(math.radians(train["direction"])) * (1/111320)
new_train_lon = train["lon"] + train["speed"] * math.sin(math.radians(train["direction"])) * (1/(111320*math.cos(math.radians(train["lat"]))))
new_dist = haversine(new_bus_lat, new_bus_lon, new_train_lat, new_train_lon)
if new_dist < dist: # 靠近中
return "WARNING", dist
return "SAFE", dist
# 模拟循环
for _ in range(3):
time.sleep(2) # 实际每2秒检查一次
for bus in buses:
for train in trains:
level, dist = check_collision(bus, train)
print(f"[{datetime.now().strftime('%H:%M:%S')}] Bus {bus['id']} vs Train {train['id']}: {level}, dist={dist:.1f}m")
if level in ("WARNING", "CRITICAL"):
# 这里调用推送:pushbullet.push_note("碰撞预警", f"距离{level}:{dist:.0f}m")
pass
说明:
- 真实场景需要从GPS模块串口读取NMEA语句,然后用pynmea2解析。火车位置可从比利时铁路的iRail API(https://irail.be)获取,该API提供实时列车位置。
- 方向角计算:建议用卡尔曼滤波平滑GPS噪声。
- 推送集成:pushbullet.py库两行代码即可。
常见问题和调试技巧
Q1: GPS误差导致误报怎么办?
- 多个连续采样取滑动平均;设置速度阈值(校车静止时不触发)。
Q2: 火车时刻表推算位置不准?
- 使用iRail API的
/liveboard/端点获取实时延误数据;若无法获取,可以缩小预警距离到300米,宁可误报不可漏报。
Q3: 校车上有多个GPS?
- 统一用一个设备作为主节点,其他作为冗余备份。
Q4: 省成本方案?
- 校车端用手机GPS + 小程序上报坐标,火车端用网上公开的实时列车地图(如OpenTrainMap)抓取。但稳定性较差,适合原型验证。
我的判断
这个原型当然不能直接部署(需硬件认证、低延迟网络、调度系统对接),但它证明了核心逻辑可以用80行代码实现。比利时事故中,如果校车配备了类似的预警系统——哪怕只是车载屏幕显示“前方500米有火车接近”——司机就有机会减速或停车。真正的价值不是代码本身,而是推动交通安全从“被动记录”转向“主动预测”。开发者现在就可以开始做两件事:
- 用自己的城市公交+地铁数据跑一遍这个逻辑,看看误报率。
- 向本地交通部门提议:用现有GPS终端加一个简单的计算模块,成本几乎为零。
技术有时做不到完美,但每减少一次碰撞都是值得的。