SUMO 与 Python 联合仿真深度剖析
第一部分:SUMO 与 Python 联合仿真基础
1. SUMO (Simulation of Urban MObility) 简介
SUMO 是一款开源、微观、多模态的交通仿真软件。它允许研究者、开发者和交通规划者模拟大规模的交通系统,包括城市路网、公共交通、行人以及不同类型的车辆。SUMO 的核心特点在于其灵活性和可扩展性,使其能够被广泛应用于交通流理论研究、交通管理策略评估、自动驾驶算法测试等多个领域。
1.1 SUMO 的核心概念
理解 SUMO 的核心概念是进行有效仿真的前提。
-
路网 (Network):
- 边 (Edge): 代表道路的一段,具有方向性。通常连接两个节点。一条物理道路的双向行驶通常由两条反向的边表示。
- 节点 (Node/Junction): 代表道路的交叉口或道路的端点。节点定义了不同边之间的连接关系和通行规则(如信号灯、优先权)。
- 车道 (Lane): 边可以包含一条或多条车道。车辆在车道上行驶,并遵循车道规则(如速度限制、允许的车辆类型)。
- 连接 (Connection): 定义了在交叉口处,车辆从一条边的某个车道如何行驶到另一条边的某个车道。连接可以有关联的信号灯相位。
- 交通信号灯 (Traffic Light System, TLS): 控制交叉口交通流的设备。SUMO 允许定义复杂的信号灯逻辑和相位。
- 类型 (Type): 可以为边和车道定义类型,例如高速公路、城市道路等,这些类型可以关联默认的速度限制、优先级等属性。
-
交通需求 (Demand/Routes):
- 车辆 (Vehicle): 仿真的基本动态单元。每辆车都有其类型(如轿车、卡车、公交车)、出发时间、路径以及特定的驾驶行为模型参数。
- 路径 (Route): 车辆从起点到终点所经过的一系列边的序列。路径可以是预定义的,也可以由 SUMO 的内置路由算法动态计算。
- 行程 (Trip): 定义车辆的起点边和终点边,以及出发时间。SUMO 可以基于行程自动计算路径。
- 车流 (Flow): 定义在特定时间段内,从某条边出发,以一定速率(如 veh/h)或数量生成的车辆。常用于模拟背景交通。
- 车辆类型 (Vehicle Type, vType): 定义车辆的物理属性(长度、最大速度、加速度、减速度等)和行为模型参数(如跟驰模型、换道模型参数)。
-
仿真控制 (Simulation Control):
- 仿真步长 (Step Length): 仿真时间推进的最小单位,通常为秒级(如1秒或0.1秒)。较小的步长可以提高仿真精度,但会增加计算时间。
- 开始与结束时间 (Begin/End Time): 定义仿真的总时长。
- 随机种子 (Seed): 用于控制仿真中的随机过程(如车辆出发时间、驾驶行为的随机性),确保仿真的可复现性。
-
输出 (Output):
- SUMO 可以生成多种类型的输出文件,记录仿真过程中的详细数据,如车辆轨迹、交叉口排队长度、行程时间、排放等。这些数据对于分析仿真结果至关重要。
1.2 SUMO 架构概述
SUMO 本身是一个命令行工具集,包含多个用于不同目的的应用程序:
sumo(或sumo-gui): 核心的仿真程序。sumo在后台运行仿真,而sumo-gui提供了一个图形用户界面,可以实时观察仿真过程。netconvert: 用于从不同格式(如 OpenStreetMap, VISUM, Vissim)导入或生成 SUMO 路网文件 (.net.xml)。也可以用于编辑和检查路网。polyconvert: 用于转换和处理地理形状数据,如建筑物、水域等,以便在sumo-gui中显示为多边形。jtrrouter: 基于行程数据和探测器数据(可选)生成车辆路径。duarouter: 基于动态用户分配 (Dynamic User Assignment) 原理生成车辆路径,试图模拟驾驶员的路径选择行为,使路网达到用户均衡状态。od2trips: 将 OD (Origin-Destination) 矩阵转换为单独的行程文件。dfrouter: 基于探测器数据(流量计数)生成路径。- 工具 (Tools): SUMO 还包含大量 Python 脚本工具(位于
sumo/tools目录下),用于数据转换、分析、可视化等辅助任务。
SUMO 的仿真过程可以概括为:
- 路网加载:
sumo程序首先加载路网文件 (.net.xml)。 - 交通需求加载: 加载车辆路径文件 (
.rou.xml) 或其他需求定义文件。 - 仿真循环:
- 时间步进: 仿真时间按照定义的步长向前推进。
- 车辆行为更新: 在每个时间步,SUMO 根据车辆的当前状态(位置、速度、加速度)、其驾驶行为模型、周围车辆状态以及路网和信号灯信息,更新所有车辆的位置和状态。这包括跟驰、换道、交叉口决策等微观行为。
- 新车辆生成: 根据需求定义,在合适的出发时间将新车辆引入路网。
- 数据输出: 根据配置,记录仿真数据。
- TraCI 交互 (如果启用): 如果有外部程序通过 TraCI 连接,SUMO 会在该步骤处理 TraCI 命令并发送数据。
- 仿真结束: 达到预设的结束时间或满足其他终止条件。
1.3 SUMO 的配置文件
SUMO 仿真通常由一个主配置文件 (.sumocfg) 来组织和启动。该文件指定了仿真所需的各种输入文件和参数。
一个简单的 .sumocfg 文件示例:
<configuration xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://sumo.dlr.de/xsd/sumoConfiguration.xsd"> <!-- XML 声明和 schema 位置 -->
<input> <!-- 输入文件配置区域 -->
<net-file value="example.net.xml"/> <!-- 指定路网文件,value 属性指向文件名 -->
<route-files value="example.rou.xml"/> <!-- 指定路径文件,可以有多个,用逗号分隔 -->
<!-- <additional-files value="tls_logic.add.xml, detectors.add.xml"/> --> <!-- 指定附加文件,如信号灯逻辑、探测器等 -->
</input>
<time> <!-- 时间相关配置区域 -->
<begin value="0"/> <!-- 仿真开始时间,单位通常是秒 -->
<end value="3600"/> <!-- 仿真结束时间,例如3600秒(1小时) -->
<step-length value="1"/> <!-- 仿真步长,例如1秒 -->
</time>
<processing> <!-- 处理相关配置区域 -->
<!-- <ignore-route-errors value="true"/> --> <!-- 是否忽略路径错误,例如车辆无法到达目的地 -->
<!-- <max-depart-delay value="60"/> --> <!-- 车辆最大允许的出发延迟时间 -->
</processing>
<routing> <!-- 路径选择相关配置 -->
<!-- <device.rerouting.period value="300"/> --> <!-- 启用车辆动态重新路由,并设置周期为300秒 -->
</routing>
<report> <!-- 报告和日志配置区域 -->
<!-- <verbose value="true"/> --> <!-- 是否输出详细日志信息 -->
<!-- <duration-log.statistics value="true"/> --> <!-- 是否记录仿真执行时间的统计信息 -->
<!-- <no-step-log value="true"/> --> <!-- 是否禁止输出每个时间步的日志 (可以减少日志量) -->
</report>
<!-- <gui_only> --> <!-- 仅用于 sumo-gui 的配置 -->
<!-- <start value="true"/> --> <!-- sumo-gui 启动时是否自动开始仿真 -->
<!-- <tracker-interval value="0.1"/> --> <!-- sumo-gui 中车辆位置更新的视觉间隔 -->
<!-- </gui_only> -->
</configuration> <!-- 配置结束标签 -->
代码解释:
<configuration>: 配置文件的根元素。xmlns:xsi和xsi:noNamespaceSchemaLocation: XML 命名空间和 schema 定义,用于验证配置文件结构。<input>: 定义所有输入文件的部分。net-file: 指定路网文件 (.net.xml)。route-files: 指定一个或多个路径文件 (.rou.xml)。additional-files: 指定附加文件,例如用于定义信号灯逻辑、探测器、可变限速标志等 (.add.xml)。
<time>: 定义仿真时间参数。begin: 仿真开始的逻辑时间。end: 仿真结束的逻辑时间。如果所有车辆都已离开路网,仿真可能会提前结束。step-length: 仿真时间步的长度,默认是1秒。更小的值如0.1秒可以获得更平滑的车辆运动和更精确的交互,但会显著增加计算量。
<processing>: 控制仿真过程中的一些行为。ignore-route-errors: 如果设置为true,即使车辆的路径存在问题(例如,路径中断或目的地不可达),仿真也会继续,问题车辆会被移除。
<routing>: 配置路径选择相关的参数,例如是否启用车辆在仿真过程中的动态路径重计算。<report>: 控制日志输出。verbose: 输出更详细的仿真过程信息。duration-log.statistics: 输出关于仿真各部分执行时间的统计信息。
<gui_only>(可选): 仅在通过sumo-gui加载此配置文件时生效的参数。
2. Python 在联合仿真中的角色
虽然 SUMO 本身功能强大,但其行为主要由预定义的配置文件和内置模型驱动。当需要更灵活的控制、更复杂的逻辑决策、与外部数据源交互或集成自定义算法时,就需要一种外部控制机制。Python,凭借其简洁的语法、丰富的库生态以及与 C++ (SUMO 的主要开发语言) 良好的交互能力,成为了与 SUMO 进行联合仿真的首选语言。
Python 在 SUMO 联合仿真中的主要角色包括:
- 动态控制仿真对象:
- 实时修改车辆属性:如速度、目的地、路径、颜色。
- 控制交通信号灯:根据自定义算法(如强化学习、模糊逻辑)动态调整信号灯相位和时长。
- 生成和移除车辆:根据外部事件或算法决策动态增减交通需求。
- 获取仿真数据:
- 实时查询车辆状态:位置、速度、加速度、所在车道、油耗、排放等。
- 获取路网信息:车道长度、限速、占用率、排队长度。
- 读取探测器数据:流量、速度、密度。
- 实现复杂算法:
- 自定义路径规划算法:例如,基于实时交通状况的动态路径诱导。
- 协同驾驶算法:模拟车联网 (V2X) 环境下的车辆协同行为。
- 交通需求预测与管理。
- 与其他系统集成:
- 连接到数据库、Web 服务获取实时数据或参数。
- 与机器学习框架 (TensorFlow, PyTorch) 集成,进行智能交通控制模型的训练和部署。
- 与外部硬件或模拟器(如车辆动力学模拟器、通信模拟器)进行协同仿真。
- 自动化仿真流程与数据分析:
- 批量运行多次仿真实验,自动调整参数。
- 使用 Pandas, NumPy, Matplotlib 等库对 SUMO 输出数据进行后处理、分析和可视化。
这种 Python 与 SUMO 的联合仿真能力,主要是通过 TraCI (Traffic Control Interface) 实现的。
3. TraCI 接口详解
TraCI 是 SUMO 提供的一个应用程序编程接口 (API),它允许外部应用程序在仿真运行时与 SUMO 进行双向通信和控制。TraCI 采用客户端-服务器 (Client-Server) 架构:SUMO 作为服务器端运行仿真,而 Python 脚本(或其他支持 TraCI 的语言编写的程序)作为客户端连接到 SUMO,发送命令并接收数据。
3.1 TraCI 的基本原理
-
启动 SUMO 服务器:
- 在启动 SUMO (或
sumo-gui) 时,需要指定一个端口号,使其作为 TraCI 服务器监听来自客户端的连接。这通常通过命令行参数--remote-port <PORT_NUMBER>实现。 - 例如:
sumo -c my_simulation.sumocfg --remote-port 8813
- 在启动 SUMO (或
-
Python 客户端连接:
- Python 脚本使用 SUMO 提供的
traciPython 库来建立与 SUMO 服务器的连接。 import tracitraci.connect(port=<PORT_NUMBER>)
- Python 脚本使用 SUMO 提供的
-
仿真步进与命令执行:
- 一旦连接建立,Python 脚本可以通过
traci.simulationStep()命令使 SUMO 仿真向前推进一个时间步。 - 在每个时间步之前或之后,Python 脚本可以调用
traci库中的各种函数来:- 获取 (Get) 仿真对象(车辆、车道、信号灯等)的状态信息。
- 设置 (Set/Change) 仿真对象的属性或行为。
- 订阅 (Subscribe) 特定对象的特定变量,以便在后续时间步高效获取其变化。
- 一旦连接建立,Python 脚本可以通过
-
关闭连接:
- 仿真结束后或不再需要交互时,客户端应关闭连接。
traci.close()
3.2 TraCI Python 库
SUMO 官方提供了一个名为 traci 的 Python 库 (通常位于 sumo/tools/traci 或通过 pip install libsumo / pip install traci (后者可能指向不同项目,推荐使用 SUMO 自带或 libsumo)),它封装了与 SUMO 进行 TCP 通信的底层细节,并提供了大量易于使用的 Python 函数,这些函数直接映射到 TraCI 定义的各种命令。
要使用 traci 库,首先需要确保 Python 能够找到它。通常有以下几种方式:
- 设置
SUMO_HOME环境变量: 将SUMO_HOME指向 SUMO 的安装根目录,然后将$SUMO_HOME/tools添加到 Python 的sys.path或PYTHONPATH环境变量中。 - 直接修改
sys.path: 在 Python 脚本的开头添加:import os, sys # 导入 os 和 sys 模块 if 'SUMO_HOME' in os.environ: # 检查 SUMO_HOME 环境变量是否存在 tools = os.path.join(os.environ['SUMO_HOME'], 'tools') # 构建 tools 目录的路径 sys.path.append(tools) # 将 tools 目录添加到 Python 的搜索路径中 else: sys.exit("please declare environment variable 'SUMO_HOME'") # 如果未设置 SUMO_HOME,则退出程序并提示 import traci # 导入 traci 库 - 安装
libsumo:libsumo是 SUMO 的一个库版本,它允许将 SUMO 作为 Python 模块直接导入和运行,traci命令通过libsumo执行,避免了 TCP 通信的开销,性能通常更好。
对于复杂的联合仿真,# 使用 libsumo 时,启动方式可能略有不同 # import libsumo as traci # 将 libsumo 导入并别名为 traci # traci.start(["sumo", "-c", "my_simulation.sumocfg"]) # 使用 libsumo 启动仿真 # ... 后续 traci 命令与标准 traci 类似libsumo通常是更推荐的方式,因为它避免了进程间通信的开销。
3.3 TraCI 命令类别与常用函数 (概览)
TraCI 提供的命令非常丰富,覆盖了仿真的方方面面。这些命令通常按照它们操作的仿真对象领域 (Domain) 进行组织。以下是一些主要领域及其常用功能的概览 (具体函数名可能略有差异,以 traci.<domain>.<functionName> 的形式调用):
-
simulation(仿真控制域):traci.simulationStep(time_step): 使仿真前进指定的逻辑时间(如果time_step未提供或为0,则前进一个仿真配置文件中定义的step-length)。这是 TraCI 交互的核心循环驱动。traci.simulation.getTime(): 获取当前仿真时间。traci.simulation.getDeltaT(): 获取当前仿真步长。traci.simulation.loadState(filepath)/traci.simulation.saveState(filepath): 加载/保存仿真状态。traci.simulation.getMinExpectedNumber(): 获取当前时间步期望的客户端数量(通常是1,如果使用多客户端则更多)。
-
vehicle(车辆域):traci.vehicle.getIDList(): 获取当前路网中所有车辆的 ID 列表。traci.vehicle.getIDCount(): 获取当前路网中车辆的总数。traci.vehicle.getPosition(vehID): 获取指定车辆的 (x, y) 坐标。traci.vehicle.getAngle(vehID): 获取车辆的行驶角度 (0度为x轴正向,逆时针增加)。traci.vehicle.getSpeed(vehID): 获取车辆的当前速度 (m/s)。traci.vehicle.getLaneID(vehID): 获取车辆当前所在车道的 ID。traci.vehicle.getRoadID(vehID): 获取车辆当前所在边的 ID。traci.vehicle.getRoute(vehID): 获取车辆当前的路径(边的列表)。traci.vehicle.setSpeed(vehID, speed): 设置车辆的目标速度。traci.vehicle.changeTarget(vehID, edgeID): 改变车辆的当前路径的下一个目标边(会触发重新规划路径到新的最终目的地)。traci.vehicle.setRoute(vehID, edgeList): 为车辆设置一条全新的路径。traci.vehicle.moveTo(vehID, laneID, pos): 将车辆移动到指定车道的指定位置 (慎用,可能破坏物理真实性)。traci.vehicle.add(vehID, routeID, typeID="DEFAULT_VEHTYPE", depart=simTime, departLane="first", departPos="base", departSpeed="0"): 在仿真中动态添加一辆新车。traci.vehicle.remove(vehID): 从仿真中移除一辆车。traci.vehicle.setColor(vehID, (R, G, B, A)): 设置车辆颜色 (A是alpha透明度, 0-255)。traci.vehicle.getLeader(vehID, dist): 获取指定车辆前方一定距离内的前车信息。traci.vehicle.getFollowSpeed(vehID, speed, gap, leaderSpeed, leaderGap): 计算基于跟驰模型的安全跟随速度。
-
lane(车道域):traci.lane.getLength(laneID): 获取车道的长度。traci.lane.getMaxSpeed(laneID): 获取车道的最大允许速度。traci.lane.getLastStepVehicleIDs(laneID): 获取上一个时间步在该车道上的车辆列表。traci.lane.getLastStepMeanSpeed(laneID): 获取上一个时间步该车道的平均车速。traci.lane.getLastStepOccupancy(laneID): 获取上一个时间步该车道的占用率。traci.lane.getWaitingTime(laneID): 获取该车道上车辆的总等待时间。
-
edge(边域):traci.edge.getLastStepVehicleIDs(edgeID): 获取上一个时间步在该边上的车辆列表。traci.edge.getLastStepMeanSpeed(edgeID): 获取上一个时间步该边的平均车速。traci.edge.getTraveltime(edgeID): 获取通过该边的(动态计算的)行程时间。traci.edge.setAllowed(edgeID, allowedVehicleClasses): 设置允许通过该边的车辆类型。
-
junction(交叉口域):traci.junction.getPosition(junctionID): 获取交叉口的 (x, y) 坐标。
-
trafficlight(或tls, 交通信号灯域):traci.trafficlight.getIDList(): 获取所有交通信号灯系统的 ID 列表。traci.trafficlight.getRedYellowGreenState(tlsID): 获取信号灯当前各进口道信号相位的状态字符串 (如 “rrgGrrgG”)。traci.trafficlight.getPhase(tlsID): 获取当前信号灯的相位索引。traci.trafficlight.setPhase(tlsID, phaseIndex): 设置信号灯到指定的相位索引。traci.trafficlight.setPhaseDuration(tlsID, phaseDuration): 设置当前相位的持续时间。traci.trafficlight.setProgram(tlsID, programID): 切换到指定的信号灯控制程序。traci.trafficlight.getCompleteRedYellowGreenDefinition(tlsID): 获取信号灯所有相位的完整定义。
-
route(路径域):traci.route.add(routeID, edgeList): 在仿真中添加一个新的路径定义。traci.route.getEdges(routeID): 获取指定路径ID包含的边列表。
-
poi(Point of Interest, 兴趣点域) 和polygon(多边形域):- 用于在
sumo-gui中添加和控制可视化的点或形状。 traci.poi.add(poiID, x, y, color, type="", layer=0)traci.polygon.add(polygonID, shape_xy_list, color, fill=True, type="", layer=0)
- 用于在
-
订阅机制 (Subscription):
- 为了高效获取大量数据,TraCI 提供了订阅机制。客户端可以预先订阅特定对象的特定变量。之后,在每个
traci.simulationStep()执行后,SUMO 会自动将这些被订阅变量的最新值发送给客户端,无需客户端为每个变量单独发送get请求。 traci.vehicle.subscribe(vehID, varIDs=[traci.constants.VAR_SPEED, traci.constants.VAR_POSITION])traci.vehicle.getSubscriptionResults(vehID)traci.constants模块定义了大量可订阅的变量常量,例如VAR_SPEED,VAR_POSITION,VAR_LANE_ID,LANE_WAITING_TIME,LAST_STEP_MEAN_SPEED等。
- 为了高效获取大量数据,TraCI 提供了订阅机制。客户端可以预先订阅特定对象的特定变量。之后,在每个
这只是 TraCI 功能的一个缩影。详细的命令列表和用法可以在 SUMO 官方文档的 TraCI 部分找到。理解这些命令的分类和作用是编写复杂 Python 控制脚本的基础。
4. 环境搭建与第一个联合仿真实例
现在,我们将通过一个简单的实例来演示如何搭建 SUMO 与 Python 的联合仿真环境,并运行一个基本的交互脚本。
4.1 环境要求与安装
-
安装 SUMO:
- 从 SUMO 官方网站 (https://sumo.dlr.de/ 或 https://eclipse.dev/sumo/) 下载适合您操作系统的最新稳定版 SUMO。
- Windows: 通常提供预编译的安装包。
- Linux: 可以通过包管理器 (如
sudo apt-get install sumo sumo-tools sumo-doc) 或从源码编译安装。 - macOS: 可以通过 Homebrew (
brew install sumo) 或从源码编译。 - 安装完成后,确保 SUMO 的
bin目录(包含sumo,sumo-gui,netconvert等可执行文件)和tools目录(包含 Python 脚本和traci库)已正确添加到系统环境变量PATH和PYTHONPATH(或SUMO_HOME已设置)。 - 验证安装:在命令行输入
sumo --version,应能看到版本信息。
-
安装 Python:
- 如果尚未安装,请从 Python 官网 (https://www.python.org/) 下载并安装 Python (推荐 3.7 或更高版本)。
- 确保 Python 的
Scripts目录(通常包含pip)已添加到系统PATH。
-
验证
traci库:- 打开 Python 解释器,尝试
import traci。如果没有错误,说明traci库可以被 Python 找到。如果找不到,请检查SUMO_HOME和PYTHONPATH设置,或者确保$SUMO_HOME/tools在sys.path中。
- 打开 Python 解释器,尝试
4.2 创建一个简单的 SUMO 场景
我们将创建一个包含一条单向道路和一个交通信号灯控制的交叉口的简单场景。
1. 定义路网节点 (example.nod.xml):
<nodes xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://sumo.dlr.de/xsd/nodes_file.xsd"> <!-- 节点文件根元素 -->
<node id="N1" x="-500.0" y="0.0" type="priority"/> <!-- 定义节点N1,坐标(-500,0),类型为优先通行(非信号灯控制) -->
<node id="N2" x="0.0" y="0.0" type="traffic_light"/> <!-- 定义节点N2(交叉口中心),坐标(0,0),类型为信号灯控制 -->
<node id="N3" x="500.0" y="0.0" type="priority"/> <!-- 定义节点N3,坐标(500,0),类型为优先通行 -->
<node id="N4" x="0.0" y="500.0" type="priority"/> <!-- 定义节点N4,坐标(0,500),类型为优先通行 -->
<node id="N5" x="0.0" y="-500.0" type="priority"/> <!-- 定义节点N5,坐标(0,-500),类型为优先通行 -->
</nodes> <!-- 节点文件结束 -->
代码解释:
<nodes>: 定义路网中所有节点(交叉口或道路端点)。<node>: 定义单个节点。id: 节点的唯一标识符。x,y: 节点的二维坐标。type: 节点的类型。priority表示由优先规则控制(例如主路优先),traffic_light表示该交叉口由信号灯控制。
2. 定义路网边 (example.edg.xml):
<edges xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://sumo.dlr.de/xsd/edges_file.xsd"> <!-- 边文件根元素 -->
<edge id="E1" from="N1" to="N2" priority="78" numLanes="2" speed="13.89"/> <!-- 定义边E1,从N1到N2,2车道,限速13.89m/s (50km/h) -->
<edge id="E2" from="N2" to="N3" priority="46" numLanes="2" speed="13.89"/> <!-- 定义边E2,从N2到N3 -->
<edge id="E3" from="N4" to="N2" priority="78" numLanes="1" speed="11.11"/> <!-- 定义边E3,从N4到N2,1车道,限速11.11m/s (40km/h) -->
<edge id="E4" from="N2" to="N5" priority="46" numLanes="1" speed="11.11"/> <!-- 定义边E4,从N2到N5 -->
</edges> <!-- 边文件结束 -->
代码解释:
<edges>: 定义路网中所有边(道路段)。<edge>: 定义单条边。id: 边的唯一标识符。from: 边的起始节点 ID。to: 边的终止节点 ID。priority: 边的优先级(数值越大优先级越高),用于netconvert确定默认的通行权和信号灯相位。numLanes: 该边包含的车道数量。speed: 该边的最大允许速度 (单位: m/s)。
3. 生成 SUMO 路网文件 (.net.xml):
打开命令行,切换到包含上述 .nod.xml 和 .edg.xml 文件的目录,然后运行 netconvert:
netconvert --node-files=example.nod.xml --edge-files=example.edg.xml --output-file=example.net.xml
这将生成 example.net.xml 文件,其中包含了 SUMO 仿真的路网拓扑、连接关系和默认的信号灯逻辑 (因为节点 N2 被定义为 traffic_light 类型,netconvert 会为其生成一个默认的信号灯方案)。
4. 定义车辆路径 (example.rou.xml):
<routes xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://sumo.dlr.de/xsd/routes_file.xsd"> <!-- 路径文件根元素 -->
<vType id="car_eastbound" accel="2.6" decel="4.5" sigma="0.5" length="5" maxSpeed="13.89" color="1,0,0"/> <!-- 定义东行车辆类型 "car_eastbound",红色 -->
<vType id="car_northbound" accel="2.6" decel="4.5" sigma="0.5" length="5" maxSpeed="11.11" color="0,0,1"/> <!-- 定义北行车辆类型 "car_northbound",蓝色 -->
<route id="route_east" edges="E1 E2"/> <!-- 定义路径 "route_east",从 E1 到 E2 -->
<route id="route_north" edges="E3 E4"/> <!-- 定义路径 "route_north",从 E3 到 E4 -->
<!-- 每隔一段时间产生车辆 -->
<flow id="flow_east" type="car_eastbound" route="route_east" begin="0" end="300" period="10" departLane="random" departSpeed="max"/> <!-- 定义东向车流,0-300秒内,每10秒一辆 -->
<flow id="flow_north" type="car_northbound" route="route_north" begin="0" end="300" period="15" departLane="random" departSpeed="max"/> <!-- 定义北向车流,0-300秒内,每15秒一辆 -->
<!-- 也可以定义单个车辆 -->
<!-- <vehicle id="veh0" type="car_eastbound" route="route_east" depart="0" departLane="0" departSpeed="10"/> --> <!-- 定义单个车辆 veh0 -->
</routes> <!-- 路径文件结束 -->
代码解释:
<routes>: 定义车辆类型、路径和交通需求。<vType>: 定义车辆类型。id: 车辆类型的唯一标识符。accel: 最大加速度 (m/s^2)。decel: 最大(舒适)减速度 (m/s^2)。sigma: 驾驶员不完美性参数 (0-1,表示驾驶行为的随机性,1为完美驾驶)。length: 车辆长度 (m)。maxSpeed: 车辆最大期望速度 (m/s)。color: 车辆在sumo-gui中显示的颜色 (R,G,B 格式,0-1 或 0-255 范围,用逗号分隔)。
<route>: 定义一条具体的路径。id: 路径的唯一标识符。edges:构成该路径的边 ID 序列,用空格分隔。
<flow>: 定义一个车流生成器。id: 车流的唯一标识符。type: 使用的车辆类型 ID。route: 使用的路径 ID。begin: 车流开始生成的时间 (s)。end: 车流结束生成的时间 (s)。period: 生成车辆的时间间隔 (s)。例如,period="10"表示大约每10秒生成一辆车。也可以用vehsPerHour或probability。departLane: 车辆出发时选择的车道 (“first”, “last”, “random”, “free”, 或具体索引)。departSpeed: 车辆出发时的速度 (“max”, “random”, 或具体值)。
<vehicle>(注释掉的部分): 定义单个车辆。属性与flow中的类似,但depart指定确切的出发时间。
5. 创建 SUMO 配置文件 (example.sumocfg):
<configuration xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://sumo.dlr.de/xsd/sumoConfiguration.xsd"> <!-- XML 声明 -->
<input> <!-- 输入文件 -->
<net-file value="example.net.xml"/> <!-- 路网文件 -->
<route-files value="example.rou.xml"/> <!-- 路径文件 -->
</input>
<time> <!-- 时间配置 -->
<begin value="0"/> <!-- 开始时间 -->
<end value="360"/> <!-- 结束时间,例如360秒 -->
<step-length value="1"/> <!-- 步长1秒 -->
</time>
<report> <!-- 报告配置 -->
<verbose value="true"/> <!-- 详细输出 -->
<duration-log.statistics value="true"/> <!-- 仿真时长统计 -->
</report>
<!-- 如果使用 sumo-gui,可以添加以下配置 -->
<gui_only> <!-- 仅用于 sumo-gui -->
<delay value="100"/> <!-- sumo-gui 中每个仿真步之间的延迟 (ms),用于减慢可视化 -->
<start value="true"/> <!-- 启动时自动开始仿真 -->
</gui_only>
</configuration> <!-- 配置结束 -->
代码解释:
- 这个配置文件指定了我们刚刚创建的路网和路径文件,并设置了仿真时长为360秒。
gui_only部分中的delay可以让sumo-gui中的仿真看起来慢一些,便于观察。
4.3 编写 Python TraCI 控制脚本 (runner.py)
现在,我们编写一个 Python 脚本,它将连接到 SUMO,运行仿真,并在每个时间步打印出当前时间和路网中的车辆数量。
import os # 导入os模块,用于操作系统相关功能,如环境变量
import sys # 导入sys模块,用于访问Python解释器相关变量和函数
import traci # 导入traci库,用于与SUMO进行交互
import time # 导入time模块,用于添加延迟等时间相关操作
# SUMO 启动命令,根据你的系统和SUMO安装位置可能需要调整
# 如果使用 sumo-gui,可以将 "sumo" 替换为 "sumo-gui"
SUMO_BINARY = "sumo" # 定义SUMO可执行文件的名称(可以是 "sumo" 或 "sumo-gui")
# SUMO_BINARY = "sumo-gui" # 如果想用图形界面,取消这行注释,并注释掉上一行
# 检查 SUMO_HOME 环境变量,并将 tools 目录添加到 Python 路径
if 'SUMO_HOME' in os.environ: # 检查 'SUMO_HOME' 是否在环境变量中定义
tools_path = os.path.join(os.environ['SUMO_HOME'], 'tools') # 构建 SUMO tools 目录的路径
if tools_path not in sys.path: # 如果 tools 目录不在 Python 的模块搜索路径中
sys.path.append(tools_path) # 将其添加进去
else: # 如果 'SUMO_HOME' 未定义
sys.exit("请设置环境变量 'SUMO_HOME' 指向您的SUMO安装目录。") # 退出脚本并提示用户设置环境变量
# SUMO 配置文件路径
sumo_config_file = "example.sumocfg" # 定义SUMO配置文件的名称
# TraCI 连接参数
traci_port = 8813 # 定义TraCI服务器将监听的端口号
use_libsumo = False # 是否使用 libsumo (默认为 False,使用 TCP 连接)
def run_simulation(): # 定义一个函数来运行仿真
"""
启动SUMO作为服务器,并使用TraCI控制仿真。
"""
if not use_libsumo: # 如果不使用 libsumo (即使用标准的 TCP TraCI 连接)
# 构建 SUMO 启动命令
sumo_cmd = [
SUMO_BINARY, # SUMO 可执行文件 (sumo 或 sumo-gui)
"-c", sumo_config_file, # "-c" 参数后跟配置文件名
"--remote-port", str(traci_port), # "--remote-port" 参数后跟指定的端口号
"--step-length", "1", # 设置仿真步长为1秒 (也可以在sumocfg中定义)
"--quit-on-end" # 仿真结束后自动退出SUMO
]
print(f"正在启动 SUMO: {
' '.join(sumo_cmd)}") # 打印将要执行的SUMO启动命令
# 在一个独立的进程中启动 SUMO
# 注意:对于更健壮的实现,可以使用 subprocess 模块并更好地管理子进程
# 这里为了简单,我们假设用户会手动先启动SUMO,或者在另一个终端启动
# 更推荐的方式是使用 subprocess.Popen 来启动和管理 SUMO 进程
# import subprocess
# sumo_proc = subprocess.Popen(sumo_cmd)
# time.sleep(2) # 等待SUMO启动
# !!! 重要提示: 为了简单起见,此脚本假设SUMO已经由用户在另一个终端手动启动,
# 或者您将在运行此Python脚本之前先启动SUMO服务器。
# 例如,手动打开一个终端并运行:
# sumo -c example.sumocfg --remote-port 8813
# 或者 sumo-gui -c example.sumocfg --remote-port 8813 --start --delay 100
#
# 如果希望脚本自动启动SUMO,请使用 subprocess 模块。
print(f"\n请确保 SUMO 服务器已在端口 {
traci_port} 上启动并加载了 {
sumo_config_file}") # 提示用户启动SUMO
print("例如,在另一个终端运行: sumo -c example.sumocfg --remote-port 8813") # 给出示例命令
input("按 Enter键 在确认SUMO已启动后继续...") # 等待用户确认
try:
# 连接到 SUMO TraCI 服务器
traci.connect(port=traci_port) # 尝试连接到指定端口的TraCI服务器
print(f"已成功连接到 SUMO (端口: {
traci_port})") # 打印连接成功信息
except traci.exceptions.TraCIException as e: # 如果连接失败,捕获TraCI异常
print(f"连接 SUMO 失败: {
e}") # 打印错误信息
print("请确保SUMO实例正在运行,并且 --remote-port 设置正确。")# 提示检查SUMO状态和端口
return # 结束函数执行
else: # 使用 libsumo
print("正在使用 libsumo 启动仿真...") # 打印使用libsumo的信息
# 注意:如果使用 libsumo,导入时通常用 import libsumo as traci
# 但如果 SUMO_HOME/tools 在路径中,原生的 traci 模块也可以被 libsumo "劫持"
# 为了清晰,假设我们已经 import traci (它可能是 libsumo 的接口)
traci.start([SUMO_BINARY, "-c", sumo_config_file, "--quit-on-end"]) # 使用traci.start(libsumo方式)启动仿真
print("libsumo 仿真已启动。") # 打印启动成功信息
# 仿真主循环
step = 0 # 初始化仿真步数计数器
max_steps = 360 # 定义最大仿真步数 (与sumocfg中的end时间对应)
try:
while step < max_steps: # 当仿真步数小于最大步数时循环
traci.simulationStep() # 执行一个仿真步
current_time = traci.simulation.getTime() # 获取当前仿真时间
vehicle_ids = traci.vehicle.getIDList() # 获取当前路网中所有车辆的ID列表
num_vehicles = len(vehicle_ids) # 计算车辆数量
print(f"仿真时间: {
current_time:.2f}s, 当前车辆数: {
num_vehicles}") # 打印当前时间和车辆数
if num_vehicles > 0 and step % 10 == 0: # 如果有车辆并且是每10步
first_veh_id = vehicle_ids[0] # 获取第一辆车的ID
speed = traci.vehicle.getSpeed(first_veh_id) # 获取该车的速度
lane = traci.vehicle.getLaneID(first_veh_id) # 获取该车所在的车道ID
print(f" -> 示例车辆 '{
first_veh_id}': 速度={
speed:.2f} m/s, 车道='{
lane}'") # 打印示例车辆信息
# 在这里可以添加更多的 TraCI 命令来控制或获取信息
# 例如,改变交通灯相位,改变车辆速度等
if current_time > 50 and current_time < 100: # 如果仿真时间在50到100秒之间
tls_ids = traci.trafficlight.getIDList() # 获取所有交通信号灯的ID
if "N2" in tls_ids: # 如果ID为 "N2" 的信号灯存在
# 简单示例:强制将N2信号灯的某个相位(例如索引0)持续时间延长
# 注意:直接控制相位可能与SUMO内置逻辑冲突,更复杂控制需禁用默认逻辑
# traci.trafficlight.setPhase("N2", 0) # 切换到相位0
# traci.trafficlight.setPhaseDuration("N2", 30) # 将当前相位持续时间设为30秒
pass # 暂时不执行实际的信号灯控制,避免与默认逻辑冲突
step += 1 # 仿真步数加1
# time.sleep(0.05) # 如果希望减慢Python脚本的执行速度(主要用于观察,不影响SUMO仿真速度)
except traci.exceptions.TraCIException as e: # 捕获仿真过程中的TraCI异常
print(f"仿真过程中发生 TraCI 错误: {
e}") # 打印错误信息
except Exception as e: # 捕获其他可能的Python异常
print(f"仿真过程中发生未知错误: {
e}") # 打印未知错误信息
finally: # 无论成功或失败,最后都执行
print("正在关闭 TraCI 连接...") # 打印关闭连接信息
traci.close() # 关闭与SUMO的TraCI连接
print("TraCI 连接已关闭。") # 打印连接已关闭信息
# if not use_libsumo and 'sumo_proc' in locals() and sumo_proc.poll() is None:
# print("正在终止 SUMO 进程...")
# sumo_proc.terminate()
# sumo_proc.wait()
# print("SUMO 进程已终止。")
if __name__ == "__main__": # 如果该脚本是主程序运行
# 确保 SUMO 配置文件存在
if not os.path.exists(sumo_config_file): # 检查配置文件是否存在
print(f"错误: SUMO 配置文件 '{
sumo_config_file}' 未找到。") # 打印错误信息
print("请先创建场景文件 (example.nod.xml, example.edg.xml, example.rou.xml) 并用 netconvert 生成 example.net.xml。") # 提示创建文件
else:
run_simulation() # 调用运行仿真函数
代码解释 (runner.py):
- 导入模块:
os,sys: 用于处理环境变量和 Python 路径。traci: SUMO TraCI 客户端库。time: (可选) 用于添加延迟,方便观察。
SUMO_BINARY: 定义了要使用的 SUMO 可执行文件。可以是sumo(命令行,无 GUI) 或sumo-gui(带图形界面)。SUMO_HOME检查: 这段代码确保$SUMO_HOME/tools目录在 Python 的模块搜索路径中,这样import traci才能成功。sumo_config_file: 指定要加载的 SUMO 配置文件。traci_port: Python 客户端将尝试连接到 SUMO 服务器的 TCP 端口。这个端口号必须与启动 SUMO 时--remote-port参数指定的端口号一致。use_libsumo: 一个布尔标志,用于选择是使用传统的 TCP TraCI 连接还是使用libsumo。run_simulation()函数:- 启动 SUMO (非
libsumo模式):- 它构建了一个
sumo_cmd列表,包含了启动 SUMO 服务器所需的命令和参数。 - 重要: 在这个简化版本中,脚本提示用户手动启动 SUMO。在实际应用中,通常会使用
subprocess.Popen在 Python 脚本中自动启动并管理 SUMO 进程。 traci.connect(port=traci_port): 尝试连接到正在运行的 SUMO 实例。
- 它构建了一个
- 启动 SUMO (
libsumo模式):traci.start([...]): 直接通过libsumo启动仿真,参数列表与命令行启动 SUMO 类似,但不包括--remote-port。
- 仿真循环 (
while step < max_steps):traci.simulationStep(): 这是核心命令。它会通知 SUMO 将仿真向前推进一个时间步(由 SUMO 配置文件中的step-length或 TraCI 命令指定)。SUMO 完成计算后,控制权返回给 Python 脚本。traci.simulation.getTime(): 获取当前的仿真时间。traci.vehicle.getIDList(): 获取当前时刻所有活动车辆的 ID 列表。- 打印当前时间和车辆数。
if num_vehicles > 0 and step % 10 == 0: 一个简单示例,每隔10步,如果路网中有车,就获取第一辆车的速度和车道ID并打印。step += 1: 增加 Python 端的步数计数器。
- 异常处理 (
try...except...finally):- 捕获
traci.exceptions.TraCIException以处理与 SUMO 通信或命令执行中可能发生的错误。 - 捕获通用的
Exception以处理其他 Python 错误。 finally块确保无论仿真是否成功完成或中途出错,traci.close()都会被调用以关闭与 SUMO 的连接。这对于释放资源和允许 SUMO 正常退出非常重要。
- 捕获
- 启动 SUMO (非
if __name__ == "__main__":: 这是 Python脚本的常用入口点,确保run_simulation()仅在脚本作为主程序直接运行时被调用。
4.4 运行第一个联合仿真
-
准备文件: 确保
example.nod.xml,example.edg.xml,example.rou.xml,example.net.xml(由netconvert生成),example.sumocfg和runner.py都在同一个目录下。 -
选择启动方式:
-
方式一:手动启动 SUMO,然后运行 Python (推荐初学者理解流程)
- 打开一个终端 (我们称之为 终端1)。
- 在 终端1 中,导航到包含所有文件的目录。
- 启动 SUMO 服务器。如果你想看图形界面:
或者,如果只想在后台运行:sumo-gui -c example.sumocfg --remote-port 8813 --start --delay 100sumo -c example.sumocfg --remote-port 8813--start会让sumo-gui自动开始仿真,--delay 100会让 GUI 中的每一步之间有100毫秒的延迟,方便观察。 - 打开另一个终端 (我们称之为 终端2)。
- 在 终端2 中,导航到相同的目录。
- 运行 Python 脚本中的提示,在
runner.py脚本中,当看到 “按 Enter键 在确认SUMO已启动后继续…” 时,按 Enter。 - 然后运行 Python 脚本:
python runner.py
-
方式二:使用
libsumo(在runner.py中设置use_libsumo = True)- 修改
runner.py,将use_libsumo = True。 - (可选) 修改
SUMO_BINARY,如果想用 GUI 并且libsumo支持 (通常是支持的),可以设为"sumo-gui"。 - 直接运行 Python 脚本:
此时,Python 脚本会通过python runner.pylibsumo直接启动和控制 SUMO。
- 修改
-
-
观察输出:
- 如果你使用了
sumo-gui,你会在图形界面中看到车辆在路网上行驶,信号灯在变化。 - 在运行 Python 脚本的终端(终端2 或直接运行
libsumo时的终端)中,你会看到类似以下的输出:正在启动 SUMO: sumo-gui -c example.sumocfg --remote-port 8813 --step-length 1 --quit-on-end (如果未使用libsumo且未手动启动) ... 请确保 SUMO 服务器已在端口 8813 上启动并加载了 example.sumocfg 例如,在另一个终端运行: sumo -c example.sumocfg --remote-port 8813 按 Enter键 在确认SUMO已启动后继续... 已成功连接到 SUMO (端口: 8813) 仿真时间: 1.00s, 当前车辆数: 0 仿真时间: 2.00s, 当前车辆数: 0 ... 仿真时间: 10.00s, 当前车辆数: 1 -> 示例车辆 'flow_east.0': 速度=13.89 m/s, 车道='E1_0' 仿真时间: 11.00s, 当前车辆数: 1 ... 仿真时间: 15.00s, 当前车辆数: 2 仿真时间: 16.00s, 当前车辆数: 2 ... 仿真时间: 20.00s, 当前车辆数: 3 -> 示例车辆 'flow_east.0': 速度=0.00 m/s, 车道='E1_0' (可能在等红灯) ... 正在关闭 TraCI 连接... TraCI 连接已关闭。
- 如果你使用了
这个简单的例子展示了 SUMO 与 Python 联合仿真的基本流程:定义 SUMO 场景 -> 编写 Python 控制脚本 -> 启动 SUMO 服务器 -> Python 客户端连接并控制 -> 结束。
第二部分:TraCI 核心控制与数据交互精解
在第一部分的基础上,我们现在将聚焦于 TraCI 的具体应用,通过丰富的代码示例来展示其强大的控制能力和数据获取能力。
2.1 车辆的精细控制与信息获取
车辆是交通仿真的核心动态元素。TraCI 提供了大量函数来实时监控和操纵车辆的行为。
2.1.1 获取车辆基本信息
在仿真过程中,我们经常需要获取车辆的各种状态信息。
# (假设 traci 已经连接,并且仿真正在运行)
# 在仿真循环的某个时间步内:
# current_sim_time = traci.simulation.getTime() # 获取当前仿真时间
vehicle_ids = traci.vehicle.getIDList() # 获取当前路网中所有活动车辆的ID列表 (返回一个字符串元组)
print(f"当前时间: {
traci.simulation.getTime():.2f}s, 车辆ID列表: {
vehicle_ids}") # 打印当前时间和车辆ID列表
if len(vehicle_ids) > 0: # 如果路网中有车辆
for veh_id in vehicle_ids: # 遍历每一辆车的ID
try:
# 获取位置 (x, y 坐标)
pos_x, pos_y = traci.vehicle.getPosition(veh_id) # 获取车辆veh_id的笛卡尔坐标(x,y)
# SUMO 内部坐标系,通常原点在路网的某个特定位置
# 获取地理位置 (经纬度,如果路网有地理参考)
# lon, lat = traci.vehicle.getPosition3D(veh_id) # 如果路网是地理参考的,这会返回(lon, lat, z)
# 或者使用 traci.simulation.convertGeo(pos_x, pos_y, fromGeo=False) 转换为经纬度
# 如果路网本身就是用经纬度定义的,getPosition 可能直接反映一定程度的地理位置
speed = traci.vehicle.getSpeed(veh_id) # 获取车辆veh_id的当前速度 (m/s)
angle = traci.vehicle.getAngle(veh_id) # 获取车辆veh_id的行驶角度 (度),0度沿X轴正向,逆时针增加
# 这个角度是车辆几何中心的朝向
road_id = traci.vehicle.getRoadID(veh_id) # 获取车辆veh_id当前所在的边的ID
lane_id = traci.vehicle.getLaneID(veh_id) # 获取车辆veh_id当前所在的车道的ID
lane_index = traci.vehicle.getLaneIndex(veh_id) # 获取车辆veh_id当前所在车道在其所在边上的索引 (从0开始,最右侧为0)
lane_position = traci.vehicle.getLanePosition(veh_id) # 获取车辆veh_id在其当前车道上的位置 (从车道起点开始计算的距离,m)
vehicle_type = traci.vehicle.getTypeID(veh_id) # 获取车辆veh_id的类型ID (例如 "car_eastbound")
route_id = traci.vehicle.getRouteID(veh_id) # 获取车辆veh_id当前使用的路径ID
current_route_edges = traci.vehicle.getRoute(veh_id) # 获取车辆veh_id当前剩余路径的边列表 (一个字符串元组)
# 累积等待时间 (车辆速度低于某一阈值时开始累积)
waiting_time = traci.vehicle.getWaitingTime(veh_id) # 获取车辆veh_id的总累积等待时间 (s)
# 车辆行驶的总距离
distance_travelled = traci.vehicle.getDistance(veh_id) # 获取车辆veh_id从出发开始已行驶的总距离 (m)
# 获取车辆的长度
length = traci.vehicle.getLength(veh_id) # 获取车辆veh_id的长度 (m)
# 获取车辆颜色 (RGBA格式,0-255)
color = traci.vehicle.getColor(veh_id) # 获取车辆veh_id的颜色,返回一个 (R, G, B, Alpha) 元组
print(f" 车辆ID: {
veh_id}") # 打印车辆ID
print(f" 位置: ({
pos_x:.2f}, {
pos_y:.2f})") # 打印车辆位置,保留两位小数
print(f" 速度: {
speed:.2f} m/s") # 打印车辆速度,保留两位小数
print(f" 角度: {
angle:.2f} 度") # 打印车辆角度,保留两位小数
print(f" 所在边: {
road_id}, 车道: {
lane_id} (索引 {
lane_index})") # 打印车辆所在边、车道和车道索引
print(f" 车道内位置: {
lane_position:.2f} m") # 打印车辆在车道内的位置,保留两位小数
print(f" 类型: {
vehicle_type}, 路径ID: {
route_id}") # 打印车辆类型和路径ID
print(f" 剩余路径: {
current_route_edges}") # 打印车辆剩余路径
print(f" 累积等待时间: {
waiting_time:.2f} s") # 打印车辆累积等待时间,保留两位小数
print(f" 已行驶距离: {
distance_travelled:.2f} m") # 打印车辆已行驶距离,保留两位小数
print(f" 长度: {
length:.2f} m, 颜色: {
color}") # 打印车辆长度和颜色
except traci.exceptions.TraCIException as e: # 捕获 TraCI 异常 (例如,车辆可能在上一个时间步离开了路网)
print(f"获取车辆 {
veh_id} 信息时出错: {
e}") # 打印错误信息
# 这种情况可能发生在该车辆刚好在这个时间步之前离开了仿真区域
# 或者ID不再有效
pass # 继续处理下一辆车
else:
print(" 路网中当前没有车辆。") # 如果路网中没有车辆,打印提示信息
代码解释与知识点:
traci.vehicle.getIDList(): 返回一个包含当前仿真中所有车辆 ID 的元组。如果路网中没有车辆,则返回空元组。traci.vehicle.getPosition(vehID): 返回车辆vehID的中心点在 SUMO 内部笛卡尔坐标系中的(x, y)坐标。这个坐标系的原点和方向取决于路网是如何创建的。traci.vehicle.getAngle(vehID): 返回车辆的朝向角度,单位是度。0 度代表车辆沿 X 轴正方向行驶,角度按逆时针方向增加。traci.vehicle.getRoadID(vehID)和traci.vehicle.getLaneID(vehID): 分别返回车辆当前所在的边 (road) 和车道 (lane) 的 ID。traci.vehicle.getLaneIndex(vehID): 对于多车道的边,返回车辆当前所在车道的索引。索引从0开始,通常0是最右侧车道(或根据路网定义)。traci.vehicle.getLanePosition(vehID): 返回车辆在当前车道上的纵向位置,即从车道起点开始计算的行驶距离。traci.vehicle.getRoute(vehID): 返回车辆当前计划要行驶的剩余路径,是一个由边 ID 组成的元组。traci.vehicle.getWaitingTime(vehID): 返回车辆自进入路网以来,因为拥堵或红灯等原因,速度低于某个阈值 (默认为 0.1 m/s) 的总累积时间。这个值对于评估交通拥堵很有用。traci.vehicle.getDistance(vehID): 返回车辆从出发点开始算起的总行驶距离。- 异常处理: 当尝试获取一个刚刚离开仿真或由于某种原因不再有效的车辆信息时,TraCI 可能会抛出
traci.exceptions.TraCIException(通常是FatalTraCIError,子类为TraCIException,或者特定错误如veículo 'ID' não é conhecido(vehicle ‘ID’ is not known))。因此,在循环处理多个车辆时,对每个车辆的操作使用try-except块是一个好习惯。
2.1.2 修改车辆状态与行为
TraCI 不仅可以获取信息,更强大的是它可以动态修改车辆的状态和行为。
# (假设 traci 已经连接,并且仿真正在运行)
# 在仿真循环的某个时间步内:
vehicle_ids = traci.vehicle.getIDList() # 获取当前路网中所有活动车辆的ID列表
if "flow_east.0" in vehicle_ids: # 假设我们知道一个特定的车辆ID,例如 "flow_east.0"
veh_id_to_control = "flow_east.0" # 定义要控制的车辆ID
try:
# 1. 修改车辆速度 (影响期望速度,实际速度受跟驰模型等限制)
target_speed = 10.0 # 设置目标速度为 10 m/s (约 36 km/h)
traci.vehicle.setSpeed(veh_id_to_control, target_speed) # 为车辆 veh_id_to_control 设置新的期望速度
print(f" 车辆 {
veh_id_to_control}: 已设置期望速度为 {
target_speed} m/s") # 打印设置速度的信息
# setSpeedMode: 控制车辆如何遵守速度限制和安全距离等规则
# mode=0: 忽略所有安全检查 (非常危险,车辆可能碰撞)
# mode=31 (默认): 遵守所有检查 (限速、安全距离、红灯等)
# traci.vehicle.setSpeedMode(veh_id_to_control, 0) # 例如,让车辆忽略安全检查 (不推荐)
# traci.vehicle.setSpeedMode(veh_id_to_control, 31) # 恢复默认安全模式
# 2. 修改车辆目标边 (会触发到该新目标边的路径重规划)
# 注意:这会改变车辆当前的 "下一个主要目标边",并可能导致其最终目的地改变
# 如果只是想改变路径中的下一条边,而最终目的地不变,使用 rerouteTraveltime 或 setRoute
current_route = traci.vehicle.getRoute(veh_id_to_control) # 获取车辆当前路径
if len(current_route) > 1: # 如果路径中至少还有两条边
# 假设我们想让它跳过下一条边,直接去下下条边 (如果合法)
# 这是一个简化的例子,实际应用中需要更复杂的逻辑
# new_target_edge = current_route[1] # 获取当前路径的第二条边作为新目标
# traci.vehicle.changeTarget(veh_id_to_control, new_target_edge) # 改变车辆的目标边
# print(f" 车辆
1973




被折叠的 条评论
为什么被折叠?



