SUMO(五)—— TraCI练习

练习内容:试着纯手工复现SUMO官网教程里面的示例:智慧交通灯。

1 复现过程

1.1 基本思路介绍

简单来说就是模拟了这样一种情况:一个最简单的路网,东西南北都是单车道,每个车道只能执行。东西方向车辆很多,南北方向只有从北到南的车,而且车辆很少。那么就设计这样一个红绿灯,让没有从北到南的车辆时,东西方向一直保持绿灯。有南北车辆经过时再改变相位。

1.2 路网文件的构建

import os
import sumolib
import numpy as np
from Constants import PREFIX, ROW_LENGTH, AVE_LENGTH

nodes = open("%s.nod.xml" % PREFIX, "w")
print("<nodes>", file = nodes)
edges = open("%s.edg.xml" % PREFIX, "w")
print("<edges>", file = edges)
connections = open("%s.con.xml" % PREFIX, "w")
print("<connections>", file = connections)

numm = 1
numx = np.array([0, -1, 0, 1, 0])
numy = np.array([0, 0, 1, 0, -1])

# Generate .nod.xml
print('    <node id="nd0" x="0" y="0" type="traffic_light"/>', file = nodes)
for i in range(1, 5) :
    print('    <node id="nd%s" x="%s" y="%s" type="priority"/>' % (numm, numx[numm] * ROW_LENGTH, numy[numm] * ROW_LENGTH), file = nodes)
    numm += 1
numm = 1
for i in range(1, 5) :
    print('    <node id="nd5_%s" x="%s" y="%s" type="priority"/>' % (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('    <edge id="e%sto0" from="nd%s" to="nd0" numLanes="1" speed="15"/>' % (i, i), file = edges)
    print('    <edge id="e0to%s" from="nd0" to="nd%s" numLanes="1" speed="15"/>' % (i, i), file = edges)
for i in range(1, 5) :
    print('    <edge id="e%sto5_%s" from="nd%s" to="nd5_%s" numLanes="1" speed="15"/>' % (i, i, i, i), file = edges)
    print('    <edge id="e5_%sto%s" from="nd5_%s" to="nd%s" numLanes="1" speed="15"/>' % (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('    <connection from="e%sto0" to="e0to%s" fromLane="0" toLane="0"/>' % (fromEdge, toEdge), file = connections)
    print('    <connection from="e0to%s" to="e%sto5_%s" fromLane="0" toLane="0"/>' % (i, i, i), file = connections)
    print('    <connection from="e5_%sto%s" to="e%sto0" fromLane="0" toLane="0"/>' % (i, i, i), file = connections)
    print('    <connection from="e5_%sto%s" to="e%sto5_%s" fromLane="0" toLane="0"/>' % (i, i, i, i), file = connections)

# FinalWork
print('</nodes>', file = nodes)
nodes.close()
print('</edges>', file = edges)
edges.close()
print('</connections>', 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

最后所画出来的路网图就会是这个样子的:

在这里插入图片描述
再细细看看交叉口处:

在这里插入图片描述
再看一眼末端处:
在这里插入图片描述

1.3 路由文件的构建

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('<routes>', file = rous)
print('    <vType id="typeOnly" accel="0.8" decel="4.5" sigma="0.5" length="5" minGap="2.5" maxSpeed="16.67"/>', file = rous)
print('', file = rous)
print('    <route id="WE" edges="e5_1to1 e1to0 e0to3 e3to5_3"/>', file = rous)
print('    <route id="EW" edges="e5_3to3 e3to0 e0to1 e1to5_1"/>', file = rous)
print('    <route id="NS" edges="e5_2to2 e2to0 e0to4 e4to5_4"/>', file = rous)
print('', file = rous)

num = 0
for i in range(0, n) :
    if(random.uniform(0, 1) < pWE) :
        print('    <vehicle id="WE_%s" type="typeOnly" route="WE" depart="%s"/>' % (num, i), file = rous)
    if(random.uniform(0, 1) < pEW) :
        print('    <vehicle id="EW_%s" type="typeOnly" route="EW" depart="%s"/>' % (num, i), file = rous)
    if(random.uniform(0, 1) < pNS) :
        print('    <vehicle id="NS_%s" type="typeOnly" route="NS" depart="%s"/>' % (num, i), file = rous)
    num += 1
print("</routes>", file=rous)
rous.close()

技术含量也是几乎没有,就是创建了一个车的种类,然后规划了一下从东到西、从西到东、从北到南的车辆分别的路线。然后每个仿真时间生成一个0~1的随机数,小于pWE就生成一个从西到东的车,小于pEW就生成一个从东到西的车,小于pNW就生成一个从北到南的车。

由于我们要从北到南的车很少,所以pNS要设置的很小才行。

1.4 add文件的构建

from Constants import PREFIX

tls = open('%s.add.xml' % PREFIX, "w")
print('<additional>', file = tls)
print('    <e1Detector id="De0" lane="e2to0_0" pos="450" freq="30" file="cross.out" friendlyPos="x"/>', file = tls)
print('</additional>', file = tls)
tls.close()

我只能说,这更没有技术含量了,不解释了。

1.5 cfg文件的构建

from Constants import PREFIX, SIMUTATION_TIME

cfg = open('%s.sumocfg' % PREFIX, "w")
print('<configuration>', file = cfg)
print('    <input>', file = cfg)
print('        <net-file value="%s.net.xml"/>' % PREFIX, file = cfg)
print('        <route-files value="%s.rou.xml"/>' % PREFIX, file = cfg)
print('        <additional-files value="%s.add.xml"/>' % PREFIX, file = cfg)
print('    </input>', file = cfg)
print('    <time>', file = cfg)
print('        <begin value="0"/>', file = cfg)
print('        <end value="%s"/>' % SIMUTATION_TIME, file = cfg)
print('    </time>', file = cfg)
print('    <processing>', file = cfg)
print('        <time-to-teleport value="-1"/>', file = cfg)
print('    </processing>', file = cfg)
print('</configuration>', file = cfg)
cfg.close()

跟1.4一样没有技术含量,不愿解释。

1.6 主函数

展示一下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之后,它会自动生成一套方案。对于我们这个极其简单的交叉口,生成的红绿灯有四个相位:

  • 第零相位:GrGr 南北方向绿灯
  • 第一相位:yryr 南北方向变成黄灯
  • 第二相位:rGrG 东西方向绿灯
  • 第三相位:ryry 东西方向变成黄灯

那么一上来就逼它是第二相位,也就是东西方向绿灯。

然后进行一步一步的仿真,在东西方向是绿灯的时候,就开始检测:如果有车从北边来了,那就切换到第三相位,也就是准备要让南北方向通行了;如果没有,那就重置第二相位的时间,也就是继续保持第二相位。这样就实现了,是不是很简单!!

(虽然简单,但还是对于TraCI的了解掌握要求蛮高的,写起来试试就知道了)

(写这个老给我一种写大模拟的感觉(或许就是大模拟?))

2 深入探索

这样确实就实现了我们所想的智能交通灯的感觉。但是,我觉得知只是这样还不够。东西方向确实是已经没有问题了,但是南北方向呢?南北方向来车了之后,就会改变相位,变成南北方向绿灯。那问题来了,南北方向的绿灯,设置为几秒比较合适呢?

为了探究这个问题,我设计了如下方案:

2.1 基本思路介绍

将南北方向的绿灯设置为不同的时间,然后分别跑一次仿真,记录东西方向的最长排队长度(其实感觉应该记录平均排队长度,写这篇文章的时候才想明白)。然后绘制一个图标,就可以将绿灯时间所对应的排队长度可视化出来,然后根据需求进行选择。

2.2 路网文件的构建

与前面一致。

2.3 路由文件的构建

2.4 add文件的构建

我的常数文件已经发生了一些变化。展示一下常数文件:

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('<additional>', file = tls)
print('    <e1Detector id="De0" lane="e2to0_0" pos="450" freq="30" file="cross.out" friendlyPos="x"/>', file = tls)
print('    <laneAreaDetector id="De1" lane="e1to0_0" pos="10" endPos="492.8" file="cr1.out" freq="30" friendlyPos="x"/>', file = tls)
print('    <laneAreaDetector id="De2" lane="e3to0_0" pos="10" endPos="492.8" file="cr2.out" freq="30" friendlyPos="x"/>', file = tls)
for i in range(1, STEP + 1) :
    print('    <tlLogic id="nd0" type="static" programID="%s" offset="0">' % i, file=tls)
    print('        <phase duration="%s" state="GrGr"/>' % (str)(BASIC_TIME + i - 1), file=tls)
    print('        <phase duration="3"  state="yryr"/>', file=tls)
    print('        <phase duration="30" state="rGrG"/>', file=tls)
    print('        <phase duration="3"  state="ryry"/>', file=tls)
    print('    </tlLogic>', file=tls)
print('</additional>', 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,分别在东西两条进口车道上,用来检测排队的车辆数。

形成之后就是这样的

在这里插入图片描述

2.5 cfg文件的构建

和前面没有区别。

2.6 主函数的构建

主文件的主函数是这样的:

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的应用。具体有什么用看一眼函数名也超级容易懂,就是返回堵车的车辆数。

2.7 结果

在这里插入图片描述

  • 5
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值