练习内容:试着纯手工复现SUMO官网教程里面的示例:智慧交通灯。
简单来说就是模拟了这样一种情况:一个最简单的路网,东西南北都是单车道,每个车道只能执行。东西方向车辆很多,南北方向只有从北到南的车,而且车辆很少。那么就设计这样一个红绿灯,让没有从北到南的车辆时,东西方向一直保持绿灯。有南北车辆经过时再改变相位。
import os
import sumolib
import numpy as np
from Constants import PREFIX, ROW_LENGTH, AVE_LENGTH
nodes = open("%s.nod.xml" % PREFIX, "w")
print("" , file = nodes)
edges = open("%s.edg.xml" % PREFIX, "w")
print("" , file = edges)
connections = open("%s.con.xml" % PREFIX, "w")
print("" , file = connections)
numm = 1
numx = np.array([0, -1, 0, 1, 0])
numy = np.array([0, 0, 1, 0, -1])
# Generate .nod.xml
print(' ', file = nodes)
for i in range(1, 5) :
print(' ' % (numm, numx[numm] * ROW_LENGTH, numy[numm] * ROW_LENGTH), file = nodes)
numm += 1
numm = 1
for i in range(1, 5) :
print(' ' % (numm, numx[numm] * (ROW_LENGTH + AVE_LENGTH), numy[numm] * (ROW_LENGTH + AVE_LENGTH)), file = nodes)
numm += 1
# Generate .edg.xml
for i in range(1, 5) :
print(' ' % (i, i), file = edges)
print(' ' % (i, i), file = edges)
for i in range(1, 5) :
print(' ' % (i, i, i, i), file = edges)
print(' ' % (i, i, i, i), file = edges)
# Generate .con.xml
for i in range(1, 5) :
fromEdge = i
if(i + 2 > 4):
toEdge = i - 2
else :
toEdge = i + 2
print(' ' % (fromEdge, toEdge), file = connections)
print(' ' % (i, i, i), file = connections)
print(' ' % (i, i, i), file = connections)
print(' ' % (i, i, i, i), file = connections)
# FinalWork
print('', file = nodes)
nodes.close()
print('', file = edges)
edges.close()
print('', file = connections)
connections.close()
path = '/usr/local/Cellar/sumo/1.13.0/share/sumo/tools'
os.system('netconvert --node-files=cross.nod.xml --edge-files=cross.edg.xml --connection-files=cross.con.xml --output-file=cross.net.xml')
其中Constants是我创建的里面包含所有常数的文件。想要修改路网的一些参数,直接修改这个文件里面的参数就可以了,很方便。
PREFIX = "cross"
ROW_LENGTH = 500
AVE_LENGTH = 10
SIMUTATION_TIME = 3600
上面的代码实在是没有什么技术含量,就是生成了nodes文件、edges文件以及connections文件,然后再调用netconvert来进行合成,来生成最终的路网文件 P R E F I X . n e t . x m l PREFIX.net.xml PREFIX.net.xml
最后所画出来的路网图就会是这个样子的:
import random
from Constants import SIMUTATION_TIME, PREFIX
random.seed(42)
pWE = 1.0 / 10
pEW = 1.0 / 11
pNS = 1.0 / 30
n = SIMUTATION_TIME
rous = open('%s.rou.xml' % PREFIX, "w")
print('' , file = rous)
print(' ', file = rous)
print('', file = rous)
print(' ', file = rous)
print(' ', file = rous)
print(' ', file = rous)
print('', file = rous)
num = 0
for i in range(0, n) :
if(random.uniform(0, 1) < pWE) :
print(' ' % (num, i), file = rous)
if(random.uniform(0, 1) < pEW) :
print(' ' % (num, i), file = rous)
if(random.uniform(0, 1) < pNS) :
print(' ' % (num, i), file = rous)
num += 1
print("", file=rous)
rous.close()
技术含量也是几乎没有,就是创建了一个车的种类,然后规划了一下从东到西、从西到东、从北到南的车辆分别的路线。然后每个仿真时间生成一个0~1的随机数,小于pWE就生成一个从西到东的车,小于pEW就生成一个从东到西的车,小于pNW就生成一个从北到南的车。
由于我们要从北到南的车很少,所以pNS要设置的很小才行。
from Constants import PREFIX
tls = open('%s.add.xml' % PREFIX, "w")
print('' , file = tls)
print(' ', file = tls)
print('', file = tls)
tls.close()
我只能说,这更没有技术含量了,不解释了。
from Constants import PREFIX, SIMUTATION_TIME
cfg = open('%s.sumocfg' % PREFIX, "w")
print('' , file = cfg)
print(' ', file = cfg)
print(' ' % PREFIX, file = cfg)
print(' ' % PREFIX, file = cfg)
print(' ' % PREFIX, file = cfg)
print(' ', file = cfg)
print(' , file = cfg)
print(' ', file = cfg)
print(' ' % SIMUTATION_TIME, file = cfg)
print(' ', file = cfg)
print(' ' , file = cfg)
print(' ', file = cfg)
print(' ', file = cfg)
print('', file = cfg)
cfg.close()
跟1.4一样没有技术含量,不愿解释。
展示一下run()函数好了!
def run() :
step = 0
traci.trafficlight.setPhase("nd0", 2)
while traci.simulation.getMinExpectedNumber() > 0: # 意思就是还有车需要处理就处理
traci.simulationStep() # 仿真一步
if(traci.trafficlight.getPhase("nd0") == 2) :
if(traci.inductionloop.getLastStepVehicleNumber("De0") > 0) :
traci.trafficlight.setPhase("nd0", 3)
else:
traci.trafficlight.setPhase("nd0", 2)
step += 1
traci.close() # 仿真结束 关闭TraCI
sys.stdout.flush() # 清除缓冲区
在这里要说一下,在把节点(node)属性设置成traffic_light之后,它会自动生成一套方案。对于我们这个极其简单的交叉口,生成的红绿灯有四个相位:
那么一上来就逼它是第二相位,也就是东西方向绿灯。
然后进行一步一步的仿真,在东西方向是绿灯的时候,就开始检测:如果有车从北边来了,那就切换到第三相位,也就是准备要让南北方向通行了;如果没有,那就重置第二相位的时间,也就是继续保持第二相位。这样就实现了,是不是很简单!!
(虽然简单,但还是对于TraCI的了解掌握要求蛮高的,写起来试试就知道了)
(写这个老给我一种写大模拟的感觉(或许就是大模拟?))
这样确实就实现了我们所想的智能交通灯的感觉。但是,我觉得知只是这样还不够。东西方向确实是已经没有问题了,但是南北方向呢?南北方向来车了之后,就会改变相位,变成南北方向绿灯。那问题来了,南北方向的绿灯,设置为几秒比较合适呢?
为了探究这个问题,我设计了如下方案:
将南北方向的绿灯设置为不同的时间,然后分别跑一次仿真,记录东西方向的最长排队长度(其实感觉应该记录平均排队长度,写这篇文章的时候才想明白)。然后绘制一个图标,就可以将绿灯时间所对应的排队长度可视化出来,然后根据需求进行选择。
与前面一致。
我的常数文件已经发生了一些变化。展示一下常数文件:
PREFIX = "cross"
ROW_LENGTH = 500
AVE_LENGTH = 10
SIMUTATION_TIME = 3600
STEP = 30
BASIC_TIME = 7
其中,BASIC_TIME指的是南北方向的绿灯从几秒开始。而STEP指的是方案数,30也就是绿灯方案包块7,8,…,36这30种方案。
from Constants import PREFIX, BASIC_TIME, STEP
tls = open('%s.add.xml' % PREFIX, "w")
print('' , file = tls)
print(' ', file = tls)
print(' ', file = tls)
print(' ', file = tls)
for i in range(1, STEP + 1) :
print(' ' % i, file=tls)
print(' ' % (str)(BASIC_TIME + i - 1), file=tls)
print(' ', file=tls)
print(' ', file=tls)
print(' ', file=tls)
print(' ', file=tls)
print('', file = tls)
tls.close()
在这里就要说一些关于交通灯的知识了。每个节点的交通灯有很多套方案,这个方案就是 p r o g r a m program program 。当你的节点设定属性为traff_light的时候,它就会自动生成一套programID=0的一套方案。如果你想要设定其他的方案,就要在 a d d . x m l add.xml add.xml 文件里面加入你的方案。
想要生成一个交通灯,那你就需要选择一个节点,并在这个节点进行创建。选择的方法就是要让 i d id id 与节点(node)的 i d id id 一致。之后生成的program,programID必须和之前的programID有所不同,尤其要注意不能为"0"。
下面的相位就是从正北开始顺时针转的,每一个行驶方向都对应一个字母才可以。大写字母不需要减速,小写字母需要减速。G是绿灯,y是黄灯,r是红灯,o是关闭。一般大小写都是按照上面这四个来用的。
在我的程序里,我就是添加了 S T E P STEP STEP 个 南北方向从 B A S I C _ T I M E BASIC\_TIME BASIC_TIME 开始的绿灯时间的交通灯,同时也增加了两个E2 Detector,分别在东西两条进口车道上,用来检测排队的车辆数。
形成之后就是这样的
和前面没有区别。
主文件的主函数是这样的:
if __name__ == '__main__' :
os.system('python GenerateAddXml.py')
os.system('python GenerateRouXml.py')
os.system('python GenerateSumocfg.py')
os.system('python GenerateNetXml.py')
sumoBinary = checkBinary('sumo')
tabl = np.zeros((3, STEP))
for i in range(0, STEP) :
tabl[1, i] = BASIC_TIME + i
traci.start([sumoBinary, "-c", "%s.sumocfg" % PREFIX, "--tripinfo-output", "tripinfo.xml"])
tabl[2, i] = run(i + 1)
ttime = tabl[1, :]
queuee = tabl[2, :]
plt.plot(ttime, queuee, marker = 'o')
plt.xlabel("Interval")
plt.ylabel("Length of Queue")
plt.show()
建立了一个名为 t a b l tabl tabl 的表格(竞赛后遗症式起名法),第0行我没用(竞赛后遗症,下次一定改),第一行就是绿灯时间,第二行就是这个绿灯时间所对应的时间。统计结束后,拆分成两个一维数组,然后用Matplotlib让他可视化。
run函数如下:
def run(time) :
step = 0
maxlength = 0
traci.trafficlight.setProgram("nd0", "%s" % time)
traci.trafficlight.setPhase("nd0", 2)
while traci.simulation.getMinExpectedNumber() > 0: # 意思就是还有车需要处理就处理
traci.simulationStep() # 仿真一步
maxlength = max(traci.lanearea.getJamLengthVehicle("De1"), traci.lanearea.getJamLengthVehicle("De2"), maxlength)
if(traci.trafficlight.getPhase("nd0") == 2) :
if(traci.inductionloop.getLastStepVehicleNumber("De0") > 0) :
traci.trafficlight.setPhase("nd0", 3)
else:
traci.trafficlight.setPhase("nd0", 2)
step += 1
if(step > 5000) :
maxlength = 100
break
traci.close() # 仿真结束 关闭TraCI
sys.stdout.flush() # 清除缓冲区
return maxlength
和上面没啥区别,不同的就是有对E2 Detector的应用。具体有什么用看一眼函数名也超级容易懂,就是返回堵车的车辆数。