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米有火车接近”——司机就有机会减速或停车。真正的价值不是代码本身,而是推动交通安全从“被动记录”转向“主动预测”。开发者现在就可以开始做两件事:

  1. 用自己的城市公交+地铁数据跑一遍这个逻辑,看看误报率。
  2. 向本地交通部门提议:用现有GPS终端加一个简单的计算模块,成本几乎为零。

技术有时做不到完美,但每减少一次碰撞都是值得的。