严正声明:本文系作者davidhopper原创,未经许可,不得转载。
SUMO
全称Simulation of Urban Mobility
,是一个开源、微观、多模态交通仿真模拟软件。TraCI
是“Traffic Control Interface”(交通控制接口)的缩写,包含在SUMO
内部, 可以通过访问正在执行的道路交通仿真情景,获取仿真对象的值并“在线”操纵其行为。简而言之,TraCI
是一个用于外部算法在线操作SUMO
仿真器的接口工具。本文以TraCIPedCrossing
为例,简介TraCI
的使用方法。
使用该示例之前,请按照我的博客Ubuntu 16.04系统中SUMO安装方法及简单示例安装SUMO
并配置环境变量SUMO_HOME
,参照另一篇博客Ubuntu 16.04系统中基于OSM创建并运行SUMO网络模型,将SUMO
源代码中的data
目录复制到/usr/share/sumo
目录。
一、将示例代码TraCIPedCrossing
复制到工作目录
如果使用apt-get
方法安装SUMO
,则示例代码TraCIPedCrossing
默认位于/usr/share/doc/sumo-doc/tutorial
目录,使用Ctrl+Alt+T
打开一个命令终端,通过如下命令将其复制到你自己的工作目录(我使用~/code/sumo-exercise
):
# 进入示例目录
cd /usr/share/doc/sumo-doc/tutorial
# 将traci_pedestrian_crossing目录复制到~/code/sumo-exercise目录
# ~/code/sumo-exercise目录是我的工作目录,你可以根据需要选择其他目录
cp -r traci_pedestrian_crossing ~/code/sumo-exercise
# 进入traci_pedestrian_crossing目录
cd ~/code/sumo-exercise/traci_pedestrian_crossing
# 刚复制过来的目录中有几个gz压缩包,需要将其解压
gzip -d runner.py.gz
gzip -d data/viewsettings.xml.gz
二、运行示例
在命令行终端中执行如下命令,运行示例:
python runner.py
此时会弹出一个SUMO-GUI
窗口,点击其中的绿色播放按钮,即可看到行人过马路的仿真场景:
三、信号逻辑及runner.py
代码解释
TraCIPedCrossing
示例中,行人过马路的红绿灯信号逻辑如下所示(见data/pedcrossing.tll.xml
):
<tlLogic id="C" type="static" programID="custom" offset="0">
<phase duration="100000" state="GGGGr"/> <!-- do not switch vehicle phase automatically -->
<phase duration="4" state="yyyyr"/>
<phase duration="10" state="rrrrG"/>
<phase duration="10" state="rrrrr"/> <!-- give pedestrians time to clear the intersection -->
</tlLogic>
默认状态是"GGGGr"
(phase = 0
)表示由绿灯转换为红灯,绿灯时间100000秒,表明该状态不会自动切换至其他状态。状态中的小写字母表示车流必须减速;
状态"yyyyr"
(phase = 1
)表示由黄灯转换为红灯,黄灯持续时间4秒;
状态"rrrrG"
(phase = 2
)表示由红灯转换为绿灯,红灯持续时间10秒;
状态"rrrrr"
(phase = 3
)表示由红灯持续状态,维持时间10秒,用于行人完全通过交叉路口。
上述状态除了phase = 0
切换到phase = 1
需要人工指定外,其余情况都会在每个状态指定的持续时间后自动切换到后续状态。
我个人认为:phase = 2
和phase = 3
写反了,正确的信号逻辑应该如下所示:
<tlLogic id="C" type="static" programID="custom" offset="0">
<phase duration="100000" state="GGGGy"/> <!-- do not switch vehicle phase automatically -->
<phase duration="4" state="yyyyr"/>
<phase duration="10" state="rrrrr"/>
<phase duration="10" state="rrrrG"/> <!-- give pedestrians time to clear the intersection -->
</tlLogic>
参照如下代码块中的注释,了解TraCI
的一般调用过程:
# 为兼容python 2.7所做的措施
from __future__ import absolute_import
from __future__ import print_function
# 导入相关包
import os
import sys
import optparse
import subprocess
import random
# 本脚本文件所在的目录
THISDIR = os.path.dirname(__file__)
# 需要从$SUMO_HOME/tools目录导入相关包,为此需要先设置环境变量SUMO_HOME,
# 若未设置,请设置之。
try:
# tutorial in tests
sys.path.append(os.path.join(THISDIR, '..', '..', '..', '..', "tools"))
# tutorial in docs
sys.path.append(os.path.join(os.environ.get("SUMO_HOME", os.path.join(
THISDIR, "..", "..", "..")), "tools"))
import traci
from sumolib import checkBinary # noqa
import randomTrips
except ImportError:
sys.exit(
"please declare environment variable 'SUMO_HOME' as the root directory of your sumo installation (it should contain folders 'bin', 'tools' and 'docs')")
# 车辆的最短绿灯时间
MIN_GREEN_TIME = 15
# 红绿灯仿真计划的初始状态,参见'pedcrossing.tll.xml'
VEHICLE_GREEN_PHASE = 0
# 红绿灯的ID(仅包含一个),默认情况下它与受控交叉路口的ID相同。
TLSID = 'C'
# 受控交叉口的人行横道边界
WALKINGAREAS = [':C_w0', ':C_w1']
CROSSINGS = [':C_c0']
# TraCI控制主循环过程
def run():
# 记录允许车辆通行的绿灯持续时间
greenTimeSoFar = 0
# 行人过马路按钮是否按下
activeRequest = False
# 主循环。在每个仿真步骤中执行某些操作,直到场景中没有新的车辆加入或现有车辆行驶
while traci.simulation.getMinExpectedNumber() > 0:
traci.simulationStep()
# 如果车辆的绿灯时间超过最短绿灯时间,则确定是否有等待过马路的行人,并切换信号灯状态
if not activeRequest:
activeRequest = checkWaitingPersons()
if traci.trafficlight.getPhase(TLSID) == VEHICLE_GREEN_PHASE:
greenTimeSoFar += 1
if greenTimeSoFar > MIN_GREEN_TIME:
# 检测行人是否按下了过马路按钮
if activeRequest:
# 信号灯切换至另一个状态,即非绿灯状态
# VEHICLE_GREEN_PHASE + 1表明切换到黄灯转红灯状态
traci.trafficlight.setPhase(
TLSID, VEHICLE_GREEN_PHASE + 1)
# 复位相关变量
activeRequest = False
greenTimeSoFar = 0
sys.stdout.flush()
traci.close()
# 检测是否有行人需要过马路
def checkWaitingPersons():
# 检测路口两侧的行人
for edge in WALKINGAREAS:
peds = traci.edge.getLastStepPersonIDs(edge)
# 检测是否有人在路口等待
# 我们假设行人在等待1秒后,才按下过马路按钮
for ped in peds:
if (traci.person.getWaitingTime(ped) == 1 and
traci.person.getNextEdge(ped) in CROSSINGS):
print("%s pushes the button" % ped)
return True
return False
# 定义本脚本文件及从命令行中解析得到的参数
def get_options():
optParser = optparse.OptionParser()
# 增加一个"--nogui"选项,默认值为False,即默认使用图形化界面
optParser.add_option("--nogui", action="store_true",
default=False, help="run the commandline version of sumo")
options, args = optParser.parse_args()
return options
# 本脚本文件主入口
if __name__ == "__main__":
# 获取程序运行的参数
options = get_options()
# 确定是调用sumo还是sumo-gui
if options.nogui:
sumoBinary = checkBinary('sumo')
else:
sumoBinary = checkBinary('sumo-gui')
net = 'pedcrossing.net.xml'
# 借助工具软件netconvert,从普通的xml输入构建多模态网络
subprocess.call([checkBinary('netconvert'),
'-c', os.path.join('data', 'pedcrossing.netccfg'),
'--output-file', net],
stdout=sys.stdout, stderr=sys.stderr)
# 随机生成仿真中的行人
randomTrips.main(randomTrips.get_options([
'--net-file', net,
'--output-trip-file', 'pedestrians.trip.xml',
'--seed', '42', # make runs reproducible
'--pedestrians',
'--prefix', 'ped',
# prevent trips that start and end on the same edge
'--min-distance', '1',
'--trip-attributes', 'departPos="random" arrivalPos="random"',
'--binomial', '4',
'--period', '35']))
# 这是启动TraCI的一般方法。sumo作为子进程启动,然后使用本脚本文件连接该子进程
traci.start([sumoBinary, '-c', os.path.join('data', 'run.sumocfg')])
# 调用TraCI控制主循环过程
run()