正好项目中用到Sumo软件, 发现网上关于sumo的中文材料非常少, 所以我想记录一些自己使用sumo过程中的经验和教训;
SUMO的官方网站是 https://sumo.dlr.de/pydoc/
本文大部分代码相关内容都来源于此网站
我的环境是macos
安装可以直接使用homebrew安装
brew install sumo
具体的安装教程在sumo官网有详细说明;
mac版的安装在这个网址
安装完后记得要在bash-profile(bash)或者zshrc中(zsh)设置SUMO_HOME, 我这里的配置是这样:
#设置sumo, 这个是用homebrew装的
export SUMO_HOME="/usr/local/opt/sumo/share/sumo"
当一切都配置完成之后, 应该可以在终端使用命令sumo
看到以下内容
~->sumo
Eclipse SUMO Version 1.3.1
Build features: Darwin-17.7.0 x86_64 Clang 10.0.0.10001044 Release Proj GUI
Copyright (C) 2001-2019 German Aerospace Center (DLR) and others; https://sumo.dlr.de
License EPL-2.0: Eclipse Public License Version 2 <https://eclipse.org/legal/epl-v20.html>
Use --help to get the list of options.
也可以使用sumo-gui
命令进入sumo的gui客户端, 在mac中是基于XQuartz的
我当时参考了这篇博文的内容veins车载通信仿真框架(2)–SUMO地图替换
大家可以去看一下这篇博文, 我简单介绍一下流程:
1.在OpenStreetMap网站上导出你想要研究的区域的地图, 在网页左上方有导出按钮, 然后选择区域之后就可以下载地图文件了, 应该是一个osm文件, 比如map.osm
2.在获得osm文件之后, 我们要用我们研究的地图替换sumo地图中的默认地图
.net.xml
道路文件, 进入osm文件所在的目录下, 使用命令netconvert --osm-files map.osm -o map.net.xml
此时我们会得到一个map.net.xml文件, 在这一步, 我们就可以在终端中输入sumo-gui
命令打开gui软件, 然后File - open network 然后选中我们生成的net.xml
地图, 就可以在软件中看到我们刚才下载的地图了
.rou.xml
车辆行为文件, 首先利用脚本randomTrips.py
生成一个.trip.xml
文件, 在这个文件中记录了随机生成的车辆的"旅程", 也就是每一辆车从哪儿开到哪儿, 至于具体的路线还是使用randomTrips
脚本生成起点到终点的最短路径, 所以这一步使用2个命令# 这一步是生成trips文件
/usr/local/opt/sumo/share/sumo/tools/randomTrips.py -n map.net.xml -e 100 -l
#生成车的路径行为xml的脚本,
#每p个步长生成1个,
#binomial二项分布系数是8, 1的时候退化成伯努利分布,
#s是种子数,
#e结束时间,
#l是根据道路长度来划分权重, L是根据车道数划分权重, 我这里选的是l;
/usr/local/Cellar/sumo/1.3.1/share/sumo/tools/randomTrips.py -n map.net.xml -r map.rou.xml -e 3600 -l --binomial=8 -p 5 -s 830 -v
.poly.xml
地形文件polyconvert --net-file map.net.xml --osm-files map.osm -o map.poly.xml
.sumo.cfg
文件<?xml version="1.0" encoding="iso-8859-1"?>
<configuration xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://sumo.sf.net/xsd/sumoConfiguration.xsd">
<input>
<net-file value="map.net.xml"/>
<route-files value="map.rou.xml"/>
<additional-files value="map.poly.xml"/>
</input>
<time>
<begin value="0"/>
<end value="1000"/>
<step-length value="0.1"/>
</time>
<report>
<no-step-log value="true"/>
</report>
<gui_only>
<start value="true"/>
</gui_only>
</configuration>
这个文件要和net, poly, rou那些文件放一起;
简单总结一下, net文件存了路网的节点和道路信息, rou存储了道路中所有车辆的信息, 比如车速, 多少辆车, 每辆车从哪儿开到哪儿这些信息, poly存储的是地形信息;
这个Traci接口是用来和Sumo模拟器通信的, 因为你不可能总是在sumo-gui里点图形化界面, 肯定得通过python, java之类的语言来和sumo通信, 靠的就是traci接口;
我尝试了java和python两种语言, java给了一个webservice的接口TraaS
和一个tra4j
的接口, 但是这两个接口都不太好用, 文档也不全, 真的要做开发的话建议可以尝试python的接口;
先讲一下用java来连sumo的过程中我遇到的一些问题;
首先呢说明一下那个tra4j很简单, 但是也是在不好用, 这里只介绍traas;
这里是traas的源码, 在github上
https://github.com/eclipse/sumo/tree/master/tools/contributed/traas
由于我没找到任何文档…只能通过猜测来用这个库了, 可能是通过运行traas/src/main/java/de/tudresden/ws/WebService.java
这个类里面的main函数, 建一个webservice项目, 然后重新建一个jws客户端去访问这个服务, 和sumo通信;
然后其他的一些功能比如获取车辆位置什么的, 都可以通过源码里给的一些接口来获取, 但是我在这里当时遇到了一些问题, 甚至连dostep都无法进行;
这里是python接口的pydoc https://sumo.dlr.de/pydoc/
这里面都有比较详细对方法的说明;
有不太好理解的方法都可以去官网查到, 我这里简单介绍一下:
首先, 我这里先导入traci库, 还有一些其他以后可能会用到的库
# coding=utf-8
import sys
import random
import sumolib
import traci # noqa
import csv
然后最基本的操作, 就是让程序跑起来
# coding=utf-8
import traci # noqa
traci.start(["sumo-gui", "-c", "/location/map.sumo.cfg", "--emission-output", "emission"], port=7911)
while traci.simulation.getMinExpectedNumber() > 0:
traci.simulationStep()
traci.close()
sys.exit()
第一步是启动traci.start, 里面给的几个参数, sumo-gui是指用这个命令来启动sumo的gui界面, 这一步要确认你在终端里面输入sumo-gui能出来sumo的gui界面, 才能继续;
然后下一个参数-c
我也不知道啥意思;
下一个参数是sumo的cfg文件的位置, 里面提供了net, rou之类的文件信息;
然后emission
这个是能耗的一些信息是我自己用的, 就不介绍了;
然后下面这一段就是让traci控制sumo开始一步步运行, 直到程序结束
while traci.simulation.getMinExpectedNumber() > 0:
traci.simulationStep()
到什么时候结束呢? 大家应该注意到在sumo.cfg文件中有下面这样的设置:
<time>
<begin value="0"/>
<end value="1000"/>
<step-length value="0.1"/>
time>
所以应该是跑1000次, 然后0.1算一次, 也就是要跑1w个时间步长;
但是我实测发现, 如果地图里还有车没跑完, sumo就会继续跑, 不会受这里设置的时长限制;
然后下面介绍traci的一些其他功能, 比如我第一步想自己实现一个车辆的寻径功能, 也就是说, 在某一个时间点, 我要加一辆车到sumo里, 比如说叫"newcar", 然后用traci控制sumo设置这辆车的路线, 颜色, 速度之类的一系列信息;
第一步是获取当前路网的拓扑图, 也就是说我要先在python里构建一张图, 这里我用的是邻接表的方式创建图, 代码如下:
首先
net = sumolib.net.readNet("/Users/zhangpeiwen/Downloads/map/cmap/map.net.xml")
newVehicletype = 'evehicle'
AdjacencyList = generateTopology()
这个generateTopology
方法如下:
def generateTopology():
AdjacencyList = {}
for e in net.getEdges():
if e.allows(newVehicletype)==False:
continue;
if AdjacencyList.__contains__(str(e.getFromNode().getID()))==False:
AdjacencyList[str(e.getFromNode().getID())]={}
AdjacencyList[str(e.getFromNode().getID())][str(e.getToNode().getID())] = e.getLanes()[0].getLength()
return AdjacencyList;
这里先从net文件中读取所有的edges和nodes信息, 然后存进AdjacencyList
里面, 这里注意我设置了一个newVehicletype
, 因为道路是有自己的特点的, 不是所有类型的车都可以在任意道路上开, 我的研究对象是电动车, 所以我只考虑电动车能走的道路, 关于所有的vehicle class的信息大家可以去官网上找, 如下:
Abstract Vehicle Class
然后到这里为止我们就获得了一个AdjacencyList
里面存着道路的拓扑图, 包括所有的道路, 节点信息;
在这里我想介绍一下sumo里的道路一些类的关系;
首先在traci for py里定义的一些类:
可以这么理解, 道路上的每一个路口都是一个node, 每条道路都是一个edge, 然后两个道路在某个点交汇会形成一个junction, 然后每一个node里可能会有很多的connection, 这个connection是用来连接两个edge的, 至于lane是车道的意思, 每一条edge里面都可能有多个lane, 一般节点的id长这样:601709881
, 然后edge的id长这样-47228917#2
, 这里的负号指的是方向, 可能存在一个edge和这条edge有一样的id, 就是负号不同, 然后后面#后面的数字指的就是车道, 就是lane编号;
然后下一步就是要把车辆加入到sumo中, 可以这么做:
def addCar():
# edge from
ef = "27437516#2"
# edge to
et = "-38280723#0"
# edge set
es = generateRoute(ef, et)
print(es)
traci.route.add(routeID="newRoute", edges=es)
traci.vehicle.add(routeID="newRoute", vehID="newCar", typeID="ElectricBus")
traci.vehicle.setVehicleClass(vehID="newCar", clazz="evehicle")
traci.vehicle.setEmissionClass(vehID="newCar", clazz="Energy/unknown")
traci.vehicle.setColor(color=(0,255,0,238), vehID="newCar")
pass;
代码意思应该都比较明显, 这里我的es = generateRoute(ef, et)
方法是给定ef和et, 会返回一个集合, 包含了从ef到et经过的所有edge的集合;
这里其实有个问题, 如果我想随机的生成一组ef和et, 怎么来做呢?
如果随机从nodelist取出两个点肯定是不可以的, 因为道路中并不一定任意两点都可达, 所以我的做法如下, 有的时候还是会有bug, 大部分时候都是正常的:
lenOfEdges = len(net.getEdges())
while True:
ef = net.getEdges()[random.randint(0,lenOfEdges-1)].getID()
et = net.getEdges()[random.randint(0,lenOfEdges-1)].getID()
if net.getEdge(ef).allows(newVehicletype)==False or net.getEdge(et).allows(newVehicletype)==False:
continue;
try:
if len(traci.simulation.findRoute(fromEdge=ef, toEdge=et, vType="DEFAULT_VEHTYPE").edges)>0:
break
except:
continue;
print "ef is "+ef+"\net is "+et
其实本质是利用traci内置的方法traci.simulation.findRoute(fromEdge=ef, toEdge=et, vType="DEFAULT_VEHTYPE").edges
去判断一下这两个点是不是可达的…这样的做法挺蠢的-.- 希望有大佬能给我提供一些更好的办法;
然后是寻找路径的办法, 这里可以直接用sumo提供的traci.simulation.findRoute(fromEdge=ef, toEdge=et, vType="DEFAULT_VEHTYPE").edges
这个方法, 直接生成es路径, 在启动sumo的时候可以设置默认寻路器使用的算法:
traci.start(["sumo-gui", "-c", "/location/map.sumo.cfg", "--start", "false", "--routing-algorithm", "astar"], port=7911)
具体的可以在这里找到Routing Algorithms
默认提供了dijkstra, a*, ch和chwrapper四种方法
但是我这里是自己实现的, 我尝试了dijkstra, a*和best-first seach三种方法, 效果上看dijkstra效果还不错, 甚至表现比默认提供的dijkstra方法更好, 我猜测可能是sumo的默认实现的寻路考虑了一些别的东西, 比如道路车辆啊什么的, 这里我没有仔细去看源码取证;
这里放一个我实现的a*寻路:
def generateRoute(ef, et, INF=float("inf")):
return generateMyRoute(ef, et)
def generateMyRoute(ef, et, INF=float("inf")):
nf = str(net.getEdge(ef).getToNode().getID())
nt = str(net.getEdge(et).getToNode().getID())
print("nf is "+nf+"\nnt is "+nt)
nodes = findNodeRoute(nf, nt)
edges = []
edges.append(ef)
s = net.getEdge(ef).getLanes()[0].getLength()
for i in range(0,len(nodes)-1):
for x in net.getNode(nodes[i]).getOutgoing():
if x.getToNode().getID()==nodes[i+1] and x.allows(newVehicletype):
edges.append(x.getID())
s+=x.getLanes()[0].getLength()
break
print len(nodes)
print len(edges)
print "dis is ",s
return edges
def findNodeRoute(nf, nt, INF=float("inf")):
openlist = {}
closelist = {}
openlist[nf] = [getF(nf, nt, 0), nf]
while openlist.__contains__(nt)==False:
u = -1
minu = INF
for x in openlist:
if openlist[x][0]<minu :
minu = openlist[x][0]
u = x
closelist[u] = openlist[u]
del openlist[u]
for x in AdjacencyList[u]:
if closelist.__contains__(x)==False:
if openlist.__contains__(x)==False:
openlist[x] = [getF(x, nt, closelist[u][0]), u]
# print(x+" is added into openlist")
else:
f = getF(x, nt, closelist[u][0])
if f<openlist[x][0]:
openlist[x] = [f, u]
if x==nt:
break
if openlist.__contains__(nt):
break
# backtrace to find the route
closelist[nt] = openlist[nt]
del openlist[nt]
u = nt
nodes = []
while u!=nf:
nodes.insert(0, u)
u = closelist[u][1]
nodes.insert(0, nf)
print "nodes are", nodes
return nodes
pass;
def getF(nf, nt, g):
return getAF(nf, nt, g)
#A* Algoritym, f = h+g
def getAF(nf, nt, g):
nfc = net.getNode(nf).getCoord()
nft = net.getNode(nt).getCoord()
return pow(pow(nfc[0]-nft[0], 2)+pow(nfc[1]-nft[1], 2), 0.5)+g
#Greedy Algorithm, Best-First Search
def getBF(nf, nt, g):
nfc = net.getNode(nf).getCoord()
nft = net.getNode(nt).getCoord()
return pow(pow(nfc[0]-nft[0], 2)+pow(nfc[1]-nft[1], 2), 0.5)
上面的代码中, 核心A部分是findNodeRoute
方法, 这里是通过A算法找到了最优路径走的所有的node, 返回一个nodelist, 然后通过generateMyRoute
方法, 把找到的nodelist转换成edgelist, 返回给es作为路径参数;
然后这里的getF
函数就是启发式函数, 我提供了两种, 一种是A*的, 也就是getAF
, 一种是Greedy Algorithm, Best-First Search, 也就是getBF
到这里就基本能实现一个简单的寻路算法了;
还有一些我用到的其他方法,
比如traci.vehicle.getWaitingTime
获取某个车辆在上一个step的等待时间;
比如traci.vehicle.getElectricityConsumption
获取某个车辆在上一个step的能源消耗;
更多的方法都可以在官网和pydoc里面找到;