SUMO笔记

SUMO 的学习笔记。

安装

1
2
3
4
5
6
sudo apt-get install cmake python g++ libxerces-c-dev libfox-1.6-dev libgdal-dev libproj-dev libgl2ps-dev swig
cd <SUMO_DIR> # please insert the correct directory name here
export SUMO_HOME="$PWD"
mkdir build/cmake-build && cd build/cmake-build
cmake ../..
make -j$(nproc)

安装

1
sudo make install
1
export SUMO_HOME=/usr/local/share/sumo/

然后cd /usr/local/share/sumo/bin,执行./sumo-gui即可。

结果如图

1563961227826
1563961227826

sumo工程结构

SUMO的仿真至少需要两个文件:

  1. 道路文件,或者叫路网文件(net.xml),就是对行车道路的描述文件;
  2. 车流文件(rou.xml),或者叫做车量行驶文件,用来描述车流量的行为。当然,更加高级的仿真可以加入别的文件,比如车辆描述文件,地形文件。

Get files

我是从www.openstreetmap.org/ 这里下载的地图

选择北京-海淀区,在左侧的export里导出。

注意,这个是所见即所得的下载,会给定当前地图页面的经纬度坐标,下载也是下载这个范围的。所以请通过缩放来控制范围的大小。

在bin文件夹里,有一个netcovert的程序,使用它可以把下载的osm(虽然后缀没有标明)文件转为需要的.net.xml文件。我下载的map文件在/home/intel/Downloads 目录下,名为map。我使用命令

1
/usr/local/share/sumo/bin$ ./netconvert --osm-files /home/intel/Downloads/map -o /home/intel/Downloads/map.net.xml

把map转为net.xml文件。

1564019933764
1564019933764

这样道路文件就做完了。

生成trips文件:

1
/usr/local/share/sumo/tools$ ./randomTrips.py -n /home/intel/Downloads/map.net.xml -l -e 600 -o /home/intel/Downloads/map.trips.xml

最后,我们把随机的旅程和道路信息结合起来就获得了车流文件(rou.xml)了。我们要用到的工具是bin文件夹下的duarouter:

1
/usr/local/share/sumo/bin$ ./duarouter -n /home/intel/Downloads/map.net.xml -t /home/intel/Downloads/map.trips.xml -o /home/intel/Downloads/map.rou.xml --ignore-errors

这样,我们就得到了rou.xml文件。

运行

仅仅靠生成的net.xml和rou.xml是不够的,还需要一个配置文件。

新建一个文件,命名为map.sumo.cfg,在其中输入

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

在终端中,输入

1
$ sumo-gui /home/intel/Downloads/map.sumo.cfg

即可运行。

效果

map1-全北京

主界面:

1564031049792
1564031049792
1564031963620
1564031963620

控制栏中,绿色三角表示开始,计时的数字表示当前的时间片,delay time表示每个时间片暂停多少时间(单位ms),一般100~250ms效果会比较好。scale traffic是控制车的数量的,这个数值越大,路上车越多,这部分好像是rou.xml之外的车。

这里我本来只想下载海淀区的地图的,结果下载成了北京市的,地图超大,路也超多。

十字路口,红黄蓝表示红绿灯。这里的灯都是会随着时间变色的。

1564024481231
1564024481231

车,黄色标记出来的部分是车。

1564024772975
1564024772975

map2-融科附近

1564031924346
1564031924346

后续随着时间的进行,能看出路上堵了很多车。

文件分析&构造map3

net.xml

打开net.xml,如图:

1564032607217
1564032607217
1564032640210
1564032640210

发现这个道路文件,抽象成图之后,就是点和边。

节点映射到现实世界就是交叉路口,边就是道路。也就是说,我们可以通过节点和边来自己构造net.xml文件。

先写一个node.xml文件,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<nodes>
<node id="node1" x="100.0" y="100.0" type="traffic_light"/>
<node id="node2" x="400.0" y="100.0" type="priority"/>
<node id="node3" x="700.0" y="100.0" type="traffic_light"/>
<node id="node4" x="100.0" y="300.0" type="traffic_light"/>
<node id="node5" x="400.0" y="300.0" type="traffic_light"/>
<node id="node6" x="700.0" y="300.0" type="traffic_light"/>
<node id="node7" x="100.0" y="600.0" type="traffic_light"/>
<node id="node8" x="400.0" y="600.0" type="traffic_light"/>
<node id="node9" x="700.0" y="600.0" type="traffic_light"/>
<node id="node10" x="100.0" y="800.0" type="traffic_light"/>
<node id="node11" x="400.0" y="800.0" type="traffic_light"/>
<node id="node12" x="700.0" y="800.0" type="priority"/>
</nodes>

id就是交叉路口的名字,x,y是交叉口的坐标,不像opencv或者显示屏驱动一下,这里的坐标就是左下角是原点。

type属性复杂一些:

  • priority: 车辆必须等待,直到它们右侧车辆完全通过路口。 Vehicles have to wait until vehicles right to them have passed the junction
  • traffic_light: 交叉口被交通灯控制着
  • right_before_left: 来自右边的车辆优先通过 Vehicles will let vehicles coming from their right side pass.

再写一个edg.xml文件,如下:

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
<edges>
<edge id="edgeR-0-0" from="node1" to="node2" priority="75" numLanes="4" speed="40" />
<edge id="edgeR-0-1" from="node2" to="node3" priority="75" numLanes="2" speed="40" />
<edge id="edgeL-0-0" from="node2" to="node1" priority="75" numLanes="4" speed="40" />
<edge id="edgeL-0-1" from="node3" to="node2" priority="75" numLanes="2" speed="40" />
<edge id="edgeU-0-0" from="node1" to="node4" priority="75" numLanes="2" speed="40" />
<edge id="edgeD-0-0" from="node4" to="node1" priority="75" numLanes="2" speed="40" />
<edge id="edgeU-0-1" from="node2" to="node5" priority="75" numLanes="2" speed="40" />
<edge id="edgeD-0-1" from="node5" to="node2" priority="75" numLanes="2" speed="40" />
<edge id="edgeU-0-2" from="node3" to="node6" priority="75" numLanes="2" speed="40" />
<edge id="edgeD-0-2" from="node6" to="node3" priority="75" numLanes="2" speed="40" />
<edge id="edgeR-1-0" from="node4" to="node5" priority="75" numLanes="2" speed="40" />
<edge id="edgeR-1-1" from="node5" to="node6" priority="75" numLanes="2" speed="40" />
<edge id="edgeL-1-0" from="node5" to="node4" priority="75" numLanes="2" speed="40" />
<edge id="edgeL-1-1" from="node6" to="node5" priority="75" numLanes="2" speed="40" />
<edge id="edgeU-1-0" from="node4" to="node7" priority="75" numLanes="2" speed="40" />
<edge id="edgeD-1-0" from="node7" to="node4" priority="75" numLanes="2" speed="40" />
<edge id="edgeU-1-1" from="node5" to="node8" priority="75" numLanes="2" speed="40" />
<edge id="edgeD-1-1" from="node8" to="node5" priority="75" numLanes="2" speed="40" />
<edge id="edgeU-1-2" from="node6" to="node9" priority="75" numLanes="2" speed="40" />
<edge id="edgeD-1-2" from="node9" to="node6" priority="75" numLanes="2" speed="40" />
<edge id="edgeR-2-0" from="node7" to="node8" priority="75" numLanes="2" speed="40" />
<edge id="edgeR-2-1" from="node8" to="node9" priority="75" numLanes="2" speed="40" />
<edge id="edgeL-2-0" from="node8" to="node7" priority="75" numLanes="2" speed="40" />
<edge id="edgeL-2-1" from="node9" to="node8" priority="75" numLanes="2" speed="40" />
<edge id="edgeU-2-0" from="node7" to="node10" priority="75" numLanes="2" speed="40" />
<edge id="edgeD-2-0" from="node10" to="node7" priority="75" numLanes="2" speed="40" />
<edge id="edgeU-2-1" from="node8" to="node11" priority="75" numLanes="2" speed="40" />
<edge id="edgeD-2-1" from="node11" to="node8" priority="75" numLanes="2" speed="40" />
<edge id="edgeU-2-2" from="node9" to="node12" priority="75" numLanes="2" speed="40" />
<edge id="edgeD-2-2" from="node12" to="node9" priority="75" numLanes="2" speed="40" />
<edge id="edgeR-3-0" from="node10" to="node11" priority="75" numLanes="2" speed="40" />
<edge id="edgeR-3-1" from="node11" to="node12" priority="75" numLanes="2" speed="40" />
<edge id="edgeL-3-0" from="node11" to="node10" priority="75" numLanes="2" speed="40" />
<edge id="edgeL-3-1" from="node12" to="node11" priority="75" numLanes="2" speed="40" />
</edges>

列出边的属性如下:

属性名 是否必须 值类型 描述
id id(string) 边的名字
formnode 边的起始节点,需在节点文件中存在
tonode 边的终止节点,需在节点文件中存在
type 类型文件中的类型名
nolanes int 边的车道数,必须是整数
speed float 边允许的最大车速(m/s),必须是浮点数
priority int 边的优先权
length float 边长(m)
shape 位置列表,用x,y表示,单位m 从形状的定义里,起止节点被忽略。例如:表示,一个边,从节点0开始,首先经过点(0,0),然后向右行100米,最后到达节点1
spread_type 枚举类型(right,center) 描述怎样延展车道:center表示双向延展车道,right为向右延展。

edge文件对于节点有两个方向,当只存在一个方向的时候,就是单行线。

由上面node.xml和edg.xml文件,可以使用netconvert转换成net.xml。

1
/usr/local/share/sumo/bin$ ./netconvert --node-files /home/intel/Documents/Maps/map3.node.xml --edge-files /home/intel/Documents/Maps/map3.edg.xml -o /home/intel/Documents/Maps/map3.net.xml
1564038194572
1564038194572

在sumo-gui中看一看结果,如图:

1564038718166
1564038718166

和我们的设定一致,一共12个node,由坐标可知,左下角是node1,下边中间是node2,类型分别为traffic_light和priority,所以下面展示的是不一样的。

rou.xml

打开rou.xml,如图

1564039572399
1564039572399

vehicle标签指的就是一辆车,而route则是这辆车将行驶过的路径。vehicle标签的id属性就是车辆的名称,depart就是出现在仿真中的时间。

自己写一个rou.xml,如下

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
<routes>
<vType id="type1" accel="0.8" decel="4.5" sigma="0.5" length="5" maxSpeed="70"/>

<vehicle id="0" depart="0.00" type="type1" color="1,0,0">
<route edges="edgeR-0-1 edgeU-0-2 edgeU-1-2 edgeL-2-1" />
</vehicle>

<vehicle id="1" depart="1.00">
<route edges="edgeR-3-1 edgeD-2-2 edgeL-2-1 edgeL-2-0 edgeU-2-0 edgeR-3-0" />
</vehicle>

<vehicle id="2" depart="2.00">
<route edges="edgeL-1-0 edgeU-1-0 edgeR-2-0 edgeU-2-1 edgeR-3-1" />
</vehicle>

<vehicle id="3" depart="3.00">
<route edges="edgeL-0-1 edgeU-0-1 edgeU-1-1 edgeL-2-0 edgeU-2-0 edgeR-3-0" />
</vehicle>

<vehicle id="4" depart="4.00">
<route edges="edgeR-3-1 edgeD-2-2 edgeL-2-1 edgeR-2-1" />
</vehicle>

<vehicle id="5" depart="5.00">
<route edges="edgeL-1-1 edgeU-1-1 edgeD-1-1" />
</vehicle>

</routes>

其中,id为0的车的颜色是红色,和其他的会有些许不同。

这种对车辆的定义不是必须的,如果没有,就会使用默认设置。

但是,如果定义行人,和车辆里面载人,则需要显示的定义。

类似于后面的type文件,这里也可以模板化。例如

1
2
3
4
5
6
7
8
9
<routes>
<vType id="type1" accel="0.8" decel="4.5" sigma="0.5" length="5" maxSpeed="70"/>

<route id="route0" color="1,1,0" edges="beg middle end rend"/>

<vehicle id="0" type="type1" route="route0" depart="0" color="1,0,0"/>
<vehicle id="1" type="type1" route="route0" depart="0" color="0,1,0"/>

</routes>

这样,type和route都可以被后面的vehicle直接调用使用。这里的地图是standard

1564041859245
1564041859245

route 必须按照开始时间排序,是因为默认是按照200时间片的间隔来加载route 的,可以使用--route-steps参数来调整,如果设置为0,即--route-steps 0表示一次性加载全部文件。

车的长度以及前面间距也是可以定义的,使用参数length描述车辆的长度,使用minGap描述最小前间距

这样,一辆车占据的空间就是

Length vs minGap.svg
Length vs minGap.svg

车辆类型可以由vClass来描述,默认类型是普通私家车。

通过如下修改

1
2
3
4
5
6
7
8
<routes>
<vType id="type1" accel="0.8" decel="4.5" sigma="0.5" length="10" maxSpeed="70" vClass="truck" />

<vehicle id="0" depart="0.00" type="type1" color="1,0,0">
<route edges="edgeR-0-1 edgeU-0-2 edgeU-1-2 edgeL-2-1" />
</vehicle>
......
</routes>

我把一辆车改为了红色,卡车,长度为10。(显示的是real word模式)

1564129059369
1564129059369

车流也是可以重复的,除了出发时间外,其具有与车辆相同的参数。

1
2
3
4
<flow id="type1" color="1,1,0"  begin="0" end= "7200" period="900" type="BUS">
<route edges="beg middle end rend"/>
<stop busStop="station1" duration="30"/>
</flow>

更多的,请查阅官方文档

con.xml

再来看sumo仿真需要的文件,官方给的资料图

SUMO使用教程(SUMO笔记/002y1HhYzy6VA1RWXH036&690.jpeg)
SUMO使用教程(SUMO笔记/002y1HhYzy6VA1RWXH036&690.jpeg)

type也比较简单,就是对edge的类型做个一个封装,这样的话描述就比较简单了。至于con.xml,就是车道合并的规则。SUMO默认是向右合并。也就是说,当三车道变成二车道的时候,右对齐,左边两个车道变成一个车道。当然啦,并不是所有的道路都是右对齐的,所以就有了这一文件的产生。

举例说明,在不加connection文件时,设置edgeU-0-2车道为3,如图

1564045108116
1564045108116

设置con.xml如下:

1
2
3
4
5
6
7
<connections>

<connection from="edgeU-0-2" to="edgeU-1-2" fromLane="0" toLane="0"/>
<connection from="edgeU-0-2" to="edgeL-1-1" fromLane="1" toLane="0"/>
<connection from="edgeU-0-2" to="edgeD-0-2" fromLane="2" toLane="1"/>

</connections>

命令

1
/usr/local/share/sumo/bin$ ./netconvert --node-files /home/intel/Documents/Maps/map3.node.xml --edge-files /home/intel/Documents/Maps/map3.edg.xml --connection-files /home/intel/Documents/Maps/map3.con.xml -o /home/intel/Documents/Maps/map3.net.xml

这样,结果如图(把左一车道由左转+掉头改为只左转,中间车道由直行改为左转)

1564045956134
1564045956134

注意:在connection标签中,没有提到的行驶路径会被认为是不允许的。

typ.xml

这个比较简单,类似于建立一个type类供edge使用。举例说明:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<edges>

<edge id="1fi" from="1" to="m1" type="b"/>
<edge id="1si" from="m1" to="0" type="a"/>
<edge id="1o" from="0" to="1" type="c"/>

<edge id="2fi" from="2" to="m2" type="b"/>
<edge id="2si" from="m2" to="0" type="a"/>
<edge id="2o" from="0" to="2" type="c"/>

<edge id="3fi" from="3" to="m3" type="b"/>
<edge id="3si" from="m3" to="0" type="a"/>
<edge id="3o" from="0" to="3" type="c"/>

<edge id="4fi" from="4" to="m4" type="b"/>
<edge id="4si" from="m4" to="0" type="a"/>
<edge id="4o" from="0" to="4" type="c"/>

</edges>
1
2
3
4
5
6
7
<types>

<type id="a" priority="3" numLanes="3" speed="13.889"/>
<type id="b" priority="2" numLanes="2" speed="11.111"/>
<type id="c" priority="1" numLanes="1" speed="11.111"/>

</types>

这样,生成命令可以写作:

1
netconvert --node-files exa.nod.xml --edge-files exa.edg.xml   --connection-files exa.con.xml --type-files exa.typ.xml   --output-file exa.net.xml

如果没有 con.xml 或者 typ.xml 则忽略对应的参数。

还有另外一种方法,直接写一个配置文件,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<configuration>
<input>
<edge-files value="exa.edg.xml"/>
<node-files value="exa.nod.xml"/>
<type-files value="exa.typ.xml"/>
<connection-files value="exa.con.xml"/>
<input>
<output>
<output-file value="exa.net.xml"/>
<output>
<processing>
<no-turnarounds value="true"/>
</processing>
</configuration>

这样一个配置文件就可以了,文件的后缀名是netc.cfg

执行

1
netconvert -c xxxx.netc.cfg

即生成net.xml文件了。

net.xml

上文中只规定了路口和道路信息,没有说明红绿灯的信息,红绿灯的信息在net.xml文件中。

搜索tlLogic关键字,如下:

1564047725414
1564047725414

tlLogic节点中id就是node的id,所以说,交通信号灯其实适合node一一对应的。

type就是交通信号灯的属性,是动态的还是静态的。动态的就是用API接口利用Phyton编程实现。这里面用静态的。programID这个就是这段交通信号灯硬编码的id,也就是说,其实交通信号灯在仿真过程中是可以改变的,而就是根据这个programID来确定需要改变的方向。offset就是这段编码启动的时间。

phase子标签:一个十字路口的红路灯的每一个不同情况都叫做一个相位,所有的相位按照顺序合在一起就是一个周期,所以说,对交通信号灯编辑,本质上就是编辑各个相位,并对其进行组合和时间设置(duration).

改变相位时长(duration)就可以改变红绿灯改变的速率。改变相位状态,就可以控制每个相位信号灯的不同通行状况。

trips.xml

打开前文中构建的trips.xml文件:

1564106619630
1564106619630

之前我们在router文件里面定义了车辆行驶的路径,很显然,相当费力气,需要一条一条的去规划,但是在trip文件中,我们只需要说明起始点就可以了,SUMO的duarouter.exe工具会自动计算最优化路径,并且生成router文件。这也就是为什么在教程一中我们randomTrips生成的是trip文件而不直接是router文件的原因。

模拟规则分析

SUMO可以看作是一种纯粹的微观(microscopic)交通模拟。每辆车给定标识符(名称),出发时间和车辆在道路网络中的路线。而一个宏观(macroscopic)交通模拟器会把整个交通流看成一个单元。SUMO还可以定义出发和到达属性,例如车道,速度或位置。每辆车分配一个类型,该类型描述车辆的物理特性和运动模型的变量。

模拟是时间离散和空间连续的,并且在内部描述每个车辆的位置,即所在的车道和从起点开始的距离。当车辆移动时,使用跟车模型(car-following model)计算车速。除了传统的交通措施外,SUMO还扩展加上了噪声排放和污染物排放/燃料消耗模型。

SUMO交通建模(Traffic Modeler)定义给定区域的交通群体总数并计算该群体的移动性愿望,作为交通模拟器的输入。

模块SUMO-ROUTER读取将要模拟的一组虚拟群体的出发时间,起点和目的地,然后使用Dijkstra路由算法计算在交通网络中的路线。

SUMO的车-驾驶员模型(Car-Driver Model)采用Gipps模型的扩展型。它能够显示交通的主要特征,如自由流和拥挤流。在每个时间步骤,车辆的速度适应于前车的速度,避免在随后的步骤中产生碰撞。该速度称为安全速度,计算

img
img

其中vl(t)是前车速度,g(t)是前车的间距,t是司机反应时间(一般1秒),而b是减速度函数。

车辆的“希望”或“期望”速度取下面三个中的最小值:可能的最大速度、车速加上最大加速度,如上计算的安全速度。因此,其期望车速为:

img
img

其中a是最大加速度。

如果模拟中认为,驾驶员会犯错没有完全适应期望的速度,这样实际速度就减去随机的“人为错误”:

img
img

其中e是扰动系数。由于车辆不得向后行驶,所以车辆当前速度是计算的速度和零的最大值。

该模型是无碰撞的,所以模拟中不允许模型的不完整造成的变异(artifact)出现。

SUMO提供V2X的可能,可以耦合外部通信仿真器,如ns2或ns3 。

SUMO给一个完整的交通需求或一组车辆会分配适当路线。其主要任务是对交通参与者选择路线(通过给定道路网络的路径)到目的地的过程建模。由于通过路线图边缘的时间很大程度上取决于使用此边缘的交通参与者数量,因此路线计算是实现大规模交通模拟的关键步骤。在SUMO,这被叫做“用户分配(user assignment)”或“交通分配(traffic assignment)”。

交通控制接口TraCI-示例

概述

TraCI提供了一个控制正在运行的交通模拟的方法。基于Client/Server模型,SUMO作为Server ,外部脚本作为Client。

TraCIPedCrossing为例,分析代码。

运行

demo位置在sumo/tests/complex/tutorial/traci_pedestrian_crossing/下。

在运行之前需要把设置

1
export SUMO_HOME=/usr/local/share/sumo/

运行该目录下的./runner.py即可(默认)看到sumo-gui,点击小三角即可播放。

1564133658760
1564133658760

修改

在之前运行过一遍的基础上

默认情况下的效果不太明显,看了一下,随机生成的行人还是比较均匀的。

修改traci_pedestrian_crossing/data下的run.sumocfg文件,把input标签下的route-files改为

1
2
3
4
5
<input>
<net-file value="../pedcrossing.net.xml"/>
<route-files value="pedcrossing.rou.xml,../pedestrians2.trip.xml"/>
<additional-files value="pedcrossing.tll.xml"/>
</input>

即由默认的pedestrians.trip.xml改为pedestrians2.trip.xml

把之前随机生成的pedestrians.trip.xml修改一下,如下是我改的结果,把300~600之间空出来了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
......    
<person id="ped8" depart="244.00" departPos="random">
<walk from="NC" to="CS" arrivalPos="random"/>
</person>
<person id="ped9" depart="270.00" departPos="random">
<walk from="NC" to="CS" arrivalPos="random"/>
</person>
<person id="ped10" depart="633.00" departPos="random">
<walk from="CS" to="NC" arrivalPos="random"/>
</person>
<person id="ped11" depart="634.00" departPos="random">
<walk from="CS" to="NC" arrivalPos="random"/>
</person>
......

这样运行的时候,效果就很明显了,300~600之间没有行人,红绿灯会保持一直都是绿灯。

1564133692244
1564133692244

分析

信号灯的逻辑文件在/dadapedcrossing.tll.xml文件中。如下

1
2
3
4
5
6
<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需要人工指定外,其余情况都会在每个状态指定的持续时间后自动切换到后续状态。

runner.py的代码分析

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
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
# 为兼容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()

TraCI分析

基本流程

官方wiki

在开启SUMO之后,客户端通过建立一个TCP连接,连接到指定的SUMO端口,TraCI支持多个客户端,并按照顺序执行客户端的所有命令。为了有一个预定义的执行顺序,每个Client应该在第一个控制命令前发出一个SetOrder命令。SetOrder命令为客户端分配一个号码,并且在同一个时间片中,把来自不同Client的命令按照编号的顺序执行(编号不需要是连续的,也不要求一定是正数,但是要求唯一)。使用多Client时,启动SUMO时需要知道Client的数量,并且所有客户端要在第一个模拟步骤(时间片)之前连接。

建立与SUMO连接的步骤如下:

Client可以向SUMO发送命令来控制模拟的运行,控制命令可以是控制单个车辆的行为,也可以是询问当前环境情况。SUMO根据请求,返回一个响应的状态响应和一个附加的结果。

Client必须使用Simulation Step命令来控制每一个时间片,如果操作执行完毕,则返回订阅的结果值(原文是 If any subscriptions have been done, the subscribed values are returned. ),在当前时间片下,所有的Client都发送完命令后,进入下一个时间片。

订阅:订阅可以被看作是一个用于检索变量的批处理模式。代替重复请求相同的变量,在每个时间步长中,你可以自动检索感兴趣的值。TraCI订阅在每一个模块基础上执行处理。您可以在每个时间步长后请求当前订阅的模块的结果。为了订阅变量,你需要直到他们的变量ID,这些ID可以在traci/constants.py文件中查询。

客户端使用close命令关闭连接,当所有的Client都发出关闭时,模拟结束,释放所有资源。

关闭连接的流程图:

SUMO作为TraCI服务器运行时,将忽略 –end<TIME> 选项,SUMO将一直运行,直到客户端要求仿真结束。 使用 SUMO-GUI 作为服务器时,必须使用 播放按钮 或在处理TraCI命令之前设置选项 –start 来启动仿真。

使用 TraCI 时,将忽略SUMO的 –end<TIME> 选项。 而是通过发出 close命令 来关闭仿真。

消息

TCP消息充当命令或结果列表的容器。因此,每个TCP消息都含有一个小标题,提供整体消息的大小和一组他后面的命令。每个命令的长度和标识符都放在命令的前面。该容器的方案如下图所示

命令的最大长度限制为255,因此在某些情况下,单个命令的长度可能不够,在这些情况下可以通过将原始ubyte长度字段设置为零并添加整数长度字段来使用扩展长度。这个命令的扩展方案如下所示:

命令的一般结构

状态响应

每个命令都由状态响应确认,包括结果和描述。标识符指的是被确认的相应命令的标识符。 结果可以具有以下值:

  • 如果成功,则为0x00
  • 如果请求的命令失败,则为0xFF
  • 如果在网络模拟器中未实现请求的命令,则为0x01(此外,必须添加描述文本)

数据类型

Atomar Types

Data type Size Description Identifier
ubyte 8 bit integer numbers (0 to 255) 0x07
byte 8 bit integer numbers (-128 to 127) 0x08
integer 32 bit integer numbers (-2^{31} to 2^{31}-1) includes bitsets with bit0 meaning the least significant bit 0x09
double 64 bit IEEE754 floating point numbers 0x0B
string variable 32 bit string length, followed by text coded as 8 bit ASCII 0x0C
stringList variable 32 bit string count n, followed by n strings 0x0E
compound object variable Compound object, internal structure depends on concrete object. The compound object identifier is always followed by an 32bit-int denoting the number of component types. Then the components are given in sequence. 0x0F

位置表示法

2D表示(ubyte identifier: 0x01):模拟网络中的笛卡尔2D位置,由两个双精度值(x和y坐标)描述。

3D表示(ubyte identifier: 0x11):模拟网络中的笛卡尔3D位置,由3个双精度值(x,y和z坐标)描述。

道路地图位置(ubyte identifier: 0x04):在大多数情况下,SUMO也可以使用其他的位置来描述,RoadID标识一条道路,Pos描述节点在道路上的位置(从0到道路的长度)。LaneID标识路段上的车道。

Lon-Lat-Position(ubyte标识符:0x00)模拟网络中地理坐标中的位置,由两个双精度值(经度和纬度)描述。

Lon-Lat-Alt-Position(ubyte标识符:0x02)模拟网络中具有高度的地理坐标中的位置,由三个双精度值(经度,纬度和高度)描述。

多边形表示 (ubyte identifier: 0x06):一系列2D点,表示多边形形状。2D点的个数是构成多边形的(x,y)点的数量。

交通灯阶段列表(ubyte identifier: 0x0D):此类型用于报告交通信号灯的不同阶段。值Length是报告连同前面和随后的道路,受各自的信号灯影响。

以下的表示符应用于存在的相位:

  • 0x01: red红灯
  • 0x02: yellow黄灯
  • 0x03: green绿灯
  • 0x04: traffic light is off and blinking交通灯关闭,闪烁
  • 0x05: traffic light is off, not blinking交通灯关闭,不闪烁

Color (ubyte identifier: 0x11)

TraCI的命令

与控制相关的命令:执行模拟步骤,关闭连接,重新加载模拟。

针对以下APIs,ID和SUMO输入文件里定义的ID相等。。 在这里,可以找到它们的一般结构:

值检索(valueretrieval)

命令结构:

variable字段取决于具体的命令,根据命令选择一个特定的值,SUMO ID是想知道该值对象的ID,它属于哪种类型的对象取决于特定的命令。

  1. 感应回路值检索(Induction loop value retrieval):检索关于感应回路的信息;
  2. 车道面积检测器值检索(lane area detector value retrieval):检索车道面积检测器的信息;
  3. 多输入/多出口检测器值检索(muli-entry/multi-exit detector valueretrieval):检索多输入多出口检测器的相关信息;
  4. 交通灯值检索(traffic lights value retrieval):检索交通信号灯的信息;
  5. 车道值检索(lane value retrieval):检索车道值信息;
  6. 车辆值检索(vechicle value retrieval):检索关于车辆的信息;
  7. 人群值检索(person value retrieval):检索关于人群的信息
  8. 车辆类型值检索(vehicle type value retrieval):检索车辆类型
  9. 路由值检索(route value retrieval):检索路由信息
  10. 兴趣点值检索(PoI value retrieval):point-of-interest兴趣点
  11. 多边形值检索(polygon value retrieval):retrieve information about polygons
  12. 结点值检索(junction value retrieval):retrieve information about junctions
  13. 街道值检索(edge value retrieval):retrieve information about edges
  14. 仿真值检索(simulation value retrieval):retrieve information about the simulation
  15. GUI值检索(GUI valueretrieval):retrieve information about the simulation visualization

SUMO的返回:

其中,variableSUMO ID是请求中的variableSUMO ID,返回类型取决于变量,可以是常规TraCI数据类型,也可以是此变量的特殊复合类型,使用特定命令记录。

状态改变statechanging

和前文的值检索相反,这一部分的命令是给特定的对象设定特定的值,命令结构如下

  1. Change Lane State change a lane's state
  2. Change Traffic Lights State change a traffic lights' state
  3. Change Vehicle State change a vehicle's state
  4. Change Person State change a persons's state
  5. Change Vehicle Type State change a vehicle type's state
  6. Change Route State change a route's state
  7. Change PoI State change a point-of-interest's state (or add/remove one)
  8. Change Polygon State change a polygon's state (or add/remove one)
  9. Change Edge State change an edge's state
  10. Change Simulation State change the simulation
  11. Change GUI State change the simulation visualization

SUMO的响应:状态改变命令的响应只有对请求的状态的响应。

另外看到了一个改变车辆状态的命令见这里

编程语言接口

  • python:the package tools/traci允许通过python可以和SUMO进行交互。(这个库满足日常测试。支持所有的TraCI命令)。
  • java:TraCI4J是关于TraCI的java应用。文档: API documentation is here
  • java,.net…….任何的符合SOAP(简单对象访问协议)的语言都可以。是一个TraCI的web服务适配器,自动适配多种语言。API完整性比TraCI4J要好,由于代码的产生基于Python的客户端。TraaS可以单独用作一个TraCI客户端,替代TraCI4J。
  • Matlab: TraCI4Matlab
  • C++:The C++ TraCIAPI是SUMO源码的一部分,为一个客户端库。
  • C++:The Veinsproject 提供使用 OMNET++编译SUMO的中间件。作为基础设施的一部分,提供针对TraCI API的C++客户端库。

V2X

TraCI允许SUMO结合网络通信模拟器甚高频全向指标,用于模拟车载通信。

TraCI allows to use SUMO in combination with communication network simulators vor simulating vehicular communication. See Topics/V2X for a list of available solutions.

其他资源

SUMO的TraCI服务是平原分布(the plain distribution)的一部分。源代码位于文件夹src/traci-server.

相关论文

  1. 车载自组网的仿真研究综述
  2. 车辆自组织网仿真研究

参考资料

  1. SUMO使用教程
  2. SUMO TraCI使用示例:TraCIPedCrossing
  3. SUMO文档010:什么是TraCI?
  4. SUMO文档014:TraCI的Python接口
  5. SUMO文档015:TraCI的协议规范
  6. SUMO-wiki-TraCI