SUMO ( Simulation of Urban Mobility) 是免费、开源的交通系统仿真软件,可以实现交通流的微观控制,即具体到道路上每一辆车的运行路线都可以单独规划。可模拟复杂环境中的交通流。

sumo中一个路网文件,分为路网net文件和交通需求(路径)route文件。net文件由node文件和edge文件组成。其中node表示节点,如一个交叉口。

在这里插入图片描述

  1. 节点文件 node file (.nod.xml)
  2. 连边文件 edge file (.edg.xml)
  3. 类型文件 edge type file (.type.xml)
  4. 基于上述三个文件创建路网文件 net file (.net.xml)
  5. 路由文件 route file (.rou.xml)

上述文件本质上都是 xml 文件,不过为了方便区分其作用,额外增加了一个后缀名。

假设我们要创建如下图所示的小型道路网络:

在这里插入图片描述

图中黑色节点对应交通路口,连边对应道路。每个路口所在位置坐标已给出。

node file

1
2
3
4
5
6
7
<nodes>
<node id="n1" x="-500" y="0" type="priority"/>
<node id="n2" x="-250" y="0" type="traffic_light"/>
<node id="n3" x="-150" y="200" type="traffic_light"/>
<node id="n4" x="0" y="0"/>
<node id="n5" x="150" y="200"/>
</nodes>

edge file

1
2
3
4
5
6
<edges>
<edge from="n1" to="n2" id="1to2" type="3L45"/>
<edge from="n2" to="n3" id="2to3" type="2L15"/>
<edge from="n3" to="n4" id="3to4" type="3L30"/>
<edge from="n4" to="n5" id="out" type="3L30"/>
</edges>

type file

1
2
3
4
5
<types>
<type id="3L45" priority="3" numLanes="3" speed="45"/>
<type id="2L15" priority="3" numLanes="2" speed="15"/>
<type id="3L30" priority="2" numLanes="3" speed="30"/>
</types>

基于以上三个文件,可以通过命令 netconvert 创建 net 文件,命令如下:

1
netconvert --node-files my_nodes.nod.xml --edge-files my_edge.edg.xml -t my_type.type.xml -o my_net.net.xml

route file

1
2
3
4
5
6
7
8
9
10
11
12
<routes>
<route id="route0" edges="1to2 2to3"/> # edges 中的基本格式为"edge1 edge2 edge3 ..."
<route id="route1" edges="2to3 3to4"/>
<route id="route2" edges="3to4 out"/>

<vType accel="1.0" decel="5.0" id="Car" length="2.0" maxSpeed="100.0" sigma="0.0"/>
<vType accel="1.0" decel="5.0" id="Bus" length="12.0" maxSpeed="1.0" sigma="0.0"/> #sigma随机程度,0 为无随机

<vehicle id="veh0" depart="10" route="route0" type="Bus"/>
<vehicle id="veh1" depart="10" route="route1" type="Car"/>
<vehicle id="veh2" depart="30" route="route2" type="Car"/>
</routes>

运行程序时需要送入一些参数,可以通过命令行形式送入,如果参数太多、太长,为了方便起见,可以将参数统一放到 xml config 文件中,在运行时,可以调用这个 config 文件。

定义 my_config_file.sumocfg

1
2
3
4
5
6
7
8
9
10
<configuration>
<input>
<net-file value="my_net.net.xml"/>
<route-files value="my_route.rou.xml"/>
</input>
<time>
<begin value="0"/>
<end value="2000"/>
</time>
</configuration>

如果一个参数既出现在了 config 文件中,又在 command line 中,则采用 command line 的设置。

一切准备就绪,下边运行程序

1
sumo-gui my_config_file.sumocfg

然后将工具栏中的 Delay 设置为 100 ms,否则仿真开始之后瞬间结束。

在这里插入图片描述

在手动构造路网 net.xml 文件时,我们也可以用 SUMO 自带的 NETEDIT 程序,通过 NETEDIT GUI 编辑路网,可能效率更高一些

上述手动设置路网的方式只适用于比较简单的情况,如果要构造与现实世界比较接近的大型路网,我们可以用下边的从外部导入 OSM (Open Street Map)路网的方法。通过搜索城市、街道找到目标道路网,然后 export 即可。

在这里插入图片描述

转化成 SUMO 路网文件

1
netconvert --osm-files map.osm -o sjtu.net.xml

以上就得到了 .net.xml 文件,这里不是通过基于 node, edge, type 文件的整合,而是直接从 osm 地图转化过来。下边就是如何得到 route 文件。

对于这种大型的路网,手动创建 route 文件也很麻烦,这里我们用 SUMO 自带的 randomTrips.py 程序创建随机的 route 文件

1
python <path_to_randomTrips.py> -n sjtu.net.xml -r sjtu.rou.xml -e 50 -l  # -e 表示 end time

最后汇总sjtu.sumocfg

1
2
3
4
5
6
7
8
9
10
<configuration>
<input>
<net-file value="sjtu.net.xml"/>
<route-files value="sjtu.rou.xml"/>
</input>
<time>
<begin value="0"/>
<end value="2000"/>
</time>
</configuration>

运行仿真,局部放大:

在这里插入图片描述

上边导入 osm 地图的方法还是比较麻烦,它主要包括 4 步:

  • 从 osm 网站获取 osm 地图
  • 用 netconvert 将 osm 地图转化成 SUMO 的 .net.xml 格式地图
  • 用 randomTrip.py 生成随机 route 文件
  • 开启仿真

实际上,SUMO 自带了一个 osmWebWizard.py 程序,整合了上述较为独立的步骤,在同一个操作界面,“一站式” 完成上述步骤。

用 osmWebWizard.py 运行仿真也是 SUMO tutorial 中的第一个项目。

1
python osmWebWizard.py

没有问题的话,应该会在浏览器中打开如下页面。这里初始地图位置是 Berlin。

在这里插入图片描述

首先是选定要仿真的地图环境。可以缩放、移动视图,通过右侧的 Select Area 可以选定一个区域。最好不要选择太大范围,否则仿真很占资源,甚至导致死机。

以上就设定好了地图和 route,点击右上方的 Generate Scenario, 就可以进入仿真界面了。

安装

安装XQuartz ,启动sumo-gui和netedit需要

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
brew install --cask xquartz 
# 安装sumo
brew tap dlr-ts/sumo
brew install sumo
# 更改变量环境
touch ~/.bashrc; open ~/.bashrc
# 在最后一行添加,其中安装路径会在安装后的终端显示。
export SUMO_HOME=/your/path/to/sumo
# 测试变量环境 重启终端,并输入
echo $SUMO_HOME
# 安装一些mac下的应用包
brew install --cask sumo-gui
# 在下载页面下载SUMO launchers
# 终端启动XQuartz 或sumo-gui

Traci接口

Traci接口是用来和sumo模拟器通信的, 因为不可能总是在sumo-gui里点图形化界面, 肯定得通过python, java之类的语言来和sumo通信, 靠的就是traci接口。

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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
import os
import sys
import traci
import random


def init_sumo(sumoBinary, sumocfg):
if 'SUMO_HOME' in os.environ:
tools = os.path.join(os.environ['SUMO_HOME'], 'tools')
sys.path.append(tools)
sumoCmd = [sumoBinary, "-c", sumocfg, "--tripinfo-output", "tripinfo.xml"]
return sumoCmd
else:
sys.exit("please declare environment variable 'SUMO_HOME'")


def generate_routefile():
random.seed(42) # make tests reproducible
N = 3600 # number of time steps
# demand per second from different directions
pWE = 1. / 10
pEW = 1. / 11
pNS = 1. / 30
with open("data/cross.rou.xml", "w") as routes:
print("""<routes>
<vType id="typeWE" accel="0.8" decel="4.5" sigma="0.5" length="5" minGap="2.5" maxSpeed="16.67" \
guiShape="passenger"/>
<vType id="typeNS" accel="0.8" decel="4.5" sigma="0.5" length="7" minGap="3" maxSpeed="25" guiShape="bus"/>

<route id="right" edges="51o 1i 2o 52i" />
<route id="left" edges="52o 2i 1o 51i" />
<route id="down" edges="54o 4i 3o 53i" />""", file=routes)
vehNr = 0
for i in range(N):
if random.uniform(0, 1) < pWE:
print(' <vehicle id="right_%i" type="typeWE" route="right" depart="%i" />' % (
vehNr, i), file=routes)
vehNr += 1
if random.uniform(0, 1) < pEW:
print(' <vehicle id="left_%i" type="typeWE" route="left" depart="%i" />' % (
vehNr, i), file=routes)
vehNr += 1
if random.uniform(0, 1) < pNS:
print(' <vehicle id="down_%i" type="typeNS" route="down" depart="%i" color="1,0,0"/>' % (
vehNr, i), file=routes)
vehNr += 1
print("</routes>", file=routes)


if __name__ == '__main__':
sumoCmd = init_sumo("sumo-gui", os.getcwd()+"/data/cross.sumocfg")
generate_routefile()

traci.start(sumoCmd)
step = 0
# we start with phase 2 where EW has green
traci.trafficlight.setPhase("0", 2)
while traci.simulation.getMinExpectedNumber() > 0:
traci.simulationStep()
if traci.trafficlight.getPhase("0") == 2:
# we are not already switching
if traci.inductionloop.getLastStepVehicleNumber("0") > 0:
# there is a vehicle from the north, switch
traci.trafficlight.setPhase("0", 3)
else:
# otherwise try to keep green for EW
traci.trafficlight.setPhase("0", 2)
step += 1
traci.close()

相关接口说明:

traci.trafficlight.setPhase 设置红绿灯的状态(tlsID, index) 第一个为交通灯ID,第二个为灯的状态,2为绿灯

getNextSwitch(string) 获取到下个信号灯相位的时间

getPhaseDuration(string) 获取该信号灯相位已经持续的时间

setPhaseDuration(string, double) 设置当前信号灯持续的时间

getMinExpectedNumber 系统仿真中车辆数。如果车辆数为0,说明所有车辆已经离开路网。仿真可以停止了

getLastStepMeanSpeed(string) -> double 可以获取车辆平均行驶速度

getLastStepVehicleIDs(string) -> list(string) 获取通过感应线圈的车辆ID

traci.inductionloop.getLastStepVehicleNumber(string) -> integer 最近一次仿真步里,指定线圈上通过车辆的数量,判断某个方向是否有车通过

traci.edge.getLastStepVehicleNumber(string) -> integer 某个路段通过车辆的数量

traci下面的vehicle类 访问车辆动作的函数:

changeTarget(string, string) -> None重新规划目的地道路

getAccel(string) -> double 获取车辆加速度

getPosition(string) -> (double, double) 获取车辆位置

isStopped(string) -> bool 检测车辆是否停止

setAccel(string, double) -> None设置车辆加速度

setMaxSpeed(string, double) -> None 设置车辆最大速度

setStop(string, string, double, integer, double, integer, double, double) -> None 设置停车时间

行人动作函数:

traci.edge.getLastStepPersonIDs(edge) 获取该edge上行人的ID

traci.person.getWaitingTime(ped) 获取指定行人的等待时间(s)

订阅subscriptions:订阅可以被看作是一个用于检索变量的批处理模式代替重复请求相同的变量,在每个时间步长中,你可以自动检索感兴趣的值。

1
2
3
4
5
6
7
8
9
10
11
12
import traci
import traci.constants as tc

PORT = 8813
traci.init(PORT)
traci.vehicle.subscribe(vehID, (tc.VAR_ROAD_ID, tc.VAR_LANEPOSITION))
print traci.vehicle.getSubscriptionResults(vehID)
for step in range(3):
print "step", step
traci.simulationStep()
print traci.vehicle.getSubscriptionResults(vehID)
traci.close()

检索的值总是从最后一个时间步骤的,它是不可能检索旧的值。

动力学模型

SUMO 中车辆动力学模型包括两方面

longitudinal model: 纵向动力学模型,描述车辆加速和减速

lateral model:横向动力学模型,描述车辆换道

纵向动力学模型方面,SUMO 主要用于研究车辆的外部行为、多车交互和交通流,对于单个车辆建模精度要求不高,可以近似看作质点。采用比较简单的 car-following model (跟车模型) 来描述车辆速度和位置变化规律。跟车模型分为两种情况:有前车和无前车。

无前车的情形,车辆保持为最大速度,这里最大速度要至少考虑三方面的因素。三个最大速度中的最小值:

  • 该类型车辆本身能够达到的最大物理速度
  • 前一时刻速度经过最大加速之后在当前时刻所能达到的最大速度
  • 当前行驶道路规定的最大速度

有前车的情形,要计算安全的行驶速度,保证任何情况下(尤其是前车急刹车时)车辆不会相撞。不同的跟车模型主要区别就在于如何计算安全行驶速度。目前 SUMO 中采用的为改进的 Krauss model.

横向动力学模型方面,SUMO采用lane changing model变道模型,简单地说就是以决策树的方式设定诸多换道条件,只要满足某些条件,就进行相应的换道操作。默认的 lane changing model 是瞬间换道,即在一个 simulation step 中完成换道,直观地看就是车辆在两个车道之间瞬移。更加精细的模型包括SublaneModel和Simple Continous lane-change model。

Krauss model

了解一下原始的 Krauss model 的建模思想。

在这里插入图片描述

泰勒展开近似替代后,得到估算值:

在这里插入图片描述

1
2
3
4
5
6
double MSCFModel_KraussOrig1::vsafe(double gap, double predSpeed, double /* predMaxDecel */) const {
...
double vsafe = (double)(-1. * myTauDecel + sqrt( myTauDecel * myTauDecel + (predSpeed * predSpeed) + (2. * myDecel * gap) ));
assert(vsafe >= 0);
return vsafe;
}

这一速度还不是最终车辆采用的跟车速度。与无前车情况类似,我们也要保证跟车速度不能超过允许的最大速度,因此要取安全速度和允许最大速度中的较小值.

改进模型与原始的 Krauss 模型的出发点是相同的:在保证不碰撞的前提下,车速尽量的快。但在计算安全速度方面,与原始 Krauss 完全不同.

没有采用泰勒展开方式近似表达刹车距离函数,而是直接数值计算。 基本思想是找到一个安全跟车速度使得后车在此速度下刹车距离 (包括反应距离) 正好等于前车的刹车距离加上原本两车间距。

在这里插入图片描述

lane changing model

道路车辆微观驾驶动力学是由以下几种模型的相互作用决定的:

  • 跟驰模型:根据前车的行为决定自身的速度。
  • 交叉口通行模型:从通行权规则、间隙接受、避免路口堵塞等方面确定车辆在不同类型交叉口的行为。
  • 换道模型:决定在多车道道路的车道选择和换道时的速度调整。

相比于其他的微观换道模型,该模型明确区分了四种不同的换道动机:

  • Strategic change 战略变道:每当车辆必须换道以便于能够驶向其行驶路径的下一条道路。
  • Cooperative change 协同变道:帮助另一辆车辆换道到他们所在的车道
  • Tactical change 战术变道:车辆试图避免跟随缓慢前车的动作,平衡从换车道中获得的预期速度收益和换车道的努力
  • Obligatory change 义务变道:清除超车车道的强迫行为可以被定义为义务行为

汽车变道规划的四个子步骤:

  • 计算优选后继车道;
  • 在保持当前车道的假设下,计算安全速度,并整合来自先前模拟步骤的车道变换相关速度请求;
  • 车道变换模型计算变更请求(左,右,停留);
  • 执行换道操作或计算下一个模拟步骤的速度请求(包括提前计划多个步骤)。是否请求速度变化取决于变道请求的紧急程度;

评估子线路的标准:

  • bestLanes(不需要换道)
  • occupation(沿着最优道路的车辆密度)
  • bestLaneOffset(车道偏移量)

评估换道行为的紧急程度:

在这里插入图片描述

探究vechicle 与blocking vehicle的关系,并根据两者之间的关系来相应地改变行为:

每当由于阻挡车辆而不能执行期望的车道变换时,车辆可以调整其速度以允许车道变换在后续步骤中成功。 此外,车辆可能对阻挡车辆的速度产生影响(实际上,这通常作为对观察自我车辆的转向信号的反应而发生)。

避免死锁:两车由于一些原因,同时到达道路的终点,此时两车都希望可以实现换道,这种情况便发生了死锁(deadlock)。

为了避免这种情况,对车进行分类(更靠近道路终点的称为blocking leader,另一个称为the blocking follower)。后者要预先进行减速,以为前车留出足够的距离进行变道操作。尽管采取这种操作,死锁仍然可能无法避免,因为会存在多车道的情况。因此,采用的方法是预留出20~40m范围进行变道