<SUMO_HOME>/docs/tutorial 中的 city_mobil 子目录包含了一个使用 SUMO 及其出租车设备来构建需求响应式交通服务的停车场仿真设置。该目录中的大多数文件都与旧版 CityMobil 教程相关,对于新版本,仅 createNetTaxi.py 是相关的。
本教程解释了如何通过使用一个(Python)脚本(我们称之为 createNetTaxi.py)生成所有输入文件,以编程方式构建此类场景。跟随本教程的最简单方法可能是将现有代码与本教程并排阅读,因为这里不会涵盖每一行代码。我们假定读者具备一些 Python 基础知识。
应用程序的大多数参数(包括可执行文件的路径)都在 constants.py 中,可以轻松地在其中进行修改以调整场景。这些参数在下文中以全大写字母表示。
在脚本的开头,我们将导入这些常量,并修改系统路径以便能够使用 sumolib(如果您通过 pip 安装了 sumolib,则此操作是可选的)。
... # 更多导入
import os
import sys
from constants import PREFIX, DOUBLE_ROWS, ROW_DIST, SLOTS_PER_ROW, SLOT_WIDTH
... # 更多常量
if 'SUMO_HOME' in os.environ:
sys.path.append(os.path.join(os.environ['SUMO_HOME'], 'tools'))
import sumolib
场景#

该设置由一个停车场组成,停车场内有几排通道,道路两侧为停车区域。载有乘客的车辆从左下角的主干道到达,驶入停车场,乘客下车,然后步行到顶部道路上的公交车站,在那里,自动化班车服务将他们带到右上角的最终目的地。班车服务将是需求驱动的,因此它只会在乘客想要上车或下车时才会停靠。
网络构建#
网络的主要参数是停车场的(双)排数(DOUBLE_ROWS)、每排的停车位数(SLOTS_PER_ROW)以及排与排之间的距离(ROW_DIST)。它们定义了布局,假设每个停车位的宽度是固定的。
我们生成一个节点文件来定义交叉口的位置,以及一个边文件来指定节点之间的连接,以及车道数量和允许的车辆类别等参数。
我们以标准文本文件的形式打开文件,并使用 sumolib 中的辅助函数来写入 XML 头:
nodes = open("%s.nod.xml" % PREFIX, "w")
sumolib.xml.writeHeader(nodes, root="nodes")
这将在我们的节点文件中生成一个像这样的头:
<?xml version="1.0" encoding="UTF-8"?>
<!-- generated on 2020-09-01 11:03:05.263997 by createNetTaxi.py v1_6_0+1864-1ecf301a37
options:
-->
<nodes xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://sumo.dlr.de/xsd/nodes_file.xsd">
这提供了关于此文件如何以及何时生成的完整信息,以及一个模式引用,允许对输入进行验证。这使得查找错误(例如属性名称中的拼写错误)变得更加容易,否则这些错误可能会被忽略。
以同样的方式打开边文件后,我们可以开始编写节点和边的定义。我们首先为插入边定义一个起始点,该点位于第一排停车场左侧 100 米处,然后在每一排的起始处定义一个交叉口:
print('<node id="in" x="-100" y="0"/>', file=nodes)
for row in range(DOUBLE_ROWS):
nextNodeID = "main%s" % row
x = row * ROW_DIST
print('<node id="%s" x="%s" y="0"/>' % (nextNodeID, x), file=nodes)
我们可以在同一个循环中生成边,这将代码扩展为:
nodeID = "main0"
print('<node id="in" x="-100" y="0"/>', file=nodes)
print('<edge id="mainin" from="in" to="%s" numLanes="2"/>' % nodeID, file=edges)
for row in range(DOUBLE_ROWS):
nextNodeID = "main%s" % row
x = row * ROW_DIST
print('<node id="%s" x="%s" y="0"/>' % (nextNodeID, x), file=nodes)
if row > 0:
print('<edge id="main%sto%s" from="%s" to="%s" numLanes="2"/>' %
(row - 1, row, nodeID, nextNodeID), file=edges)
nodeID = nextNodeID
请注意,我们需要跟踪前一个节点的 ID,并且边的数量比节点少一个。除了车道数量(为 2,以允许缓慢转弯进入停车场的车辆超车)之外,边几乎没有其他附加参数。
(赛博)巴士边的代码遵循相同的模式,但稍微复杂一些,因为它还需要一条相反方向的街道,因为巴士需要掉头。此外,它还需要一条人行道(建模为行人使用的车道)。生成两条停车街道之间前向和后向连接的代码如下:
print("""<edge id="%s" from="cyber%s" to="cyber%s" numLanes="3" spreadType="center">
<lane index="0" allow="pedestrian" width="2.00"/>
<lane index="1" allow="taxi bus"/>
<lane index="2" allow="taxi bus"/>
</edge>""" % (edgeID, row - 1, row), file=edges)
print("""<edge id="-%s" from="cyber%s" to="cyber%s" numLanes="2" spreadType="center">
<lane index="0" allow="taxi bus"/>
<lane index="1" allow="taxi bus"/>
</edge>""" % (edgeID, row, row - 1), file=edges)
请注意,反向行驶的街道没有人行道,因为乘客只允许在停车场一侧上下车。
现在我们只需要纵向行驶的街道,基本网络就完成了。这里我们不需要更多的节点——我们只需要连接现有的部分——但是我们需要再次在两侧设置人行道,以便人们步行去乘坐巴士。
for row in range(DOUBLE_ROWS):
print("""<edge id="road%s" from="main%s" to="cyber%s" numLanes="2">
<lane index="0" allow="pedestrian" width="2.00"/>
<lane index="1" disallow="pedestrian"/>
</edge>""" % (row, row, row), file=edges)
print("""<edge id="-road%s" from="cyber%s" to="main%s" numLanes="2">
<lane index="0" allow="pedestrian" width="2.00"/>
<lane index="1" disallow="pedestrian"/>
</edge>""" % (row, row, row), file=edges)
现在我们可以关闭文件并运行 netconvert:
subprocess.call([sumolib.checkBinary('netconvert'),
'-n', '%s.nod.xml' % PREFIX,
'-e', '%s.edg.xml' % PREFIX,
'-o', '%s.net.xml' % PREFIX])
您已经可以用 netedit 或 sumo-gui 打开生成的网络,它看起来会像上面的图片,除了我们接下来要添加的停车位和公交车站。
附加基础设施和车辆类型#
对于公交车站和停车区域,我们使用一个所谓的附加文件,我们像上面一样打开它并写入一个头。停车区域的定义如下:
for row in range(DOUBLE_ROWS):
print("""
<parkingArea id="ParkArea%s" lane="road%s_1"
roadsideCapacity="%s" angle="270" length="8"/>
<parkingArea id="ParkArea-%s" lane="-road%s_1"
roadsideCapacity="%s" angle="270" length="8"/>""" %
(row, row, SLOTS_PER_ROW, row, row, SLOTS_PER_ROW), file=stops)
这里我们使用了一个小技巧。由于 SUMO 将停车位附加到车辆车道(不是最右侧的车道),它会阻挡(仅是视觉上的,但仍然是)人行道,所以我们稍微将停车位扩大到 8 米,并定义一个角度,让所有车辆向前停放,为行人留出一些空间。
对于公交车站,唯一的小惊喜是我们需要的比停车位少一个,因为它们只位于停车道路之间。
for row in range(DOUBLE_ROWS-1):
edgeID = "cyber%sto%s" % (row, row + 1)
print(' <busStop id="%sstop" lane="%s_1" startPos="2" endPos="12"/>' % (edgeID, edgeID), file=stops)
现在我们可以关闭附加文件,并加载它来查看基础设施。
然而,我们将重用附加文件来开始定义车辆(至少是车辆类型)。大多数车辆类型的定义是为了外观(颜色)和车辆类别,以便它只使用正确的道路。赛博车还有一个出租车设备来响应乘客请求。我们还将定义一辆常规巴士,以便在本教程的后续迭代中与常规巴士服务进行比较。
print((""" <vType id="car" color="0.7,0.7,0.7"/>
<vType id="ped_pedestrian" vClass="pedestrian" color="1,0.2,0.2"/>
<vType id="cybercar" vClass="taxi" length="%s" minGap="1" guiShape="evehicle" maxSpeed="%s"
color="green" emissionClass="HBEFA2/P_7_7" personCapacity="%s">
<param key="has.taxi.device" value="true"/>
</vType>
<vType id="bus" vClass="bus" color="blue" personCapacity="%s"/>
""") % (CYBER_LENGTH, CYBER_SPEED, CYBER_CAPACITY, BUS_CAPACITY), file=stops)
现在我们准备好定义交通需求了。
交通需求#
我们在不同的路线文件中定义赛博车和乘客车辆。对于赛博车,一个简单的流量就足够了:
print(""" <flow id="c" type="cybercar" begin="50" period="100" number="%s" line="taxi">
<route edges="cyberin cyber0to1"/>
</flow>""" % (TOTAL_CAPACITY // CYBER_CAPACITY), file=routes)
这里唯一特殊的是 line 属性,它使用保留字符串 "taxi" 来表示这将是一个需求驱动的运输系统,响应特殊的运输请求。这个特性还会处理车辆在到达其初始路线终点后不应离开场景,而是等待新请求的事实。
乘客的定义稍微复杂一些,因为他们乘坐自己的汽车到达,开车到停车场,离开汽车,步行到公交车站,然后乘坐赛博车前往最终目的地。我们在这里通过两个嵌套循环完成所有操作,因为我们希望填满所有排的所有停车位。
for v in range(SLOTS_PER_ROW):
for idx in range(DOUBLE_ROWS):
由于乘客需要在他们的汽车中开始,我们需要首先定义汽车:
print(""" <trip id="v%s.%s" type="car" depart="%s" from="mainin" to="road%s">
<stop parkingArea="ParkArea%s" duration="1000"/>
</trip>""" % (idx, v, v * period, idx, idx), file=routes)
现在我们为每辆车生成一个随机数量的乘客,每个乘客都有自己的计划:
print(""" <person id="%sp%s" type="ped_pedestrian" depart="triggered">
<ride from="mainin" to="%sroad%s" lines="%s"/>
<walk busStop="%s"/>
<ride to="cyberout" lines="taxi"/>
</person>""" % (vehId, p, infix, idx, vehId, busStop), file=routes)
第一次乘坐使用私家车到停车场,然后我们步行到公交车站并乘坐赛博车。"triggered" 出发是为了让乘客在汽车中开始。
就是这样!我们将所有内容汇总到一个漂亮的配置文件中,该文件将网络、附加文件(用于公交车站和停车场)、赛博路线文件和乘客路线文件绑定在一起。
print("""<configuration>
<input>
<net-file value="%s.net.xml"/>
<route-files value="%s_cyber.rou.xml,%s_demand%02i.rou.xml"/>
<additional-files value="%s.add.xml"/>
<no-step-log value="True"/>
<time-to-teleport value="0"/>
<device.taxi.dispatch-algorithm value="routeExtension"/>
</input>
</configuration>""" % (PREFIX, PREFIX, PREFIX, period, PREFIX), file=config)
这里需要注意的一点是调度算法的使用,它将决定赛博车如何选择下一个要服务的请求。
运行场景生成和场景#
最后,我们可以通过双击脚本运行它,或者如果您在 Linux / MacOS 控制台上:
./createNetTaxi.py
它将为不同的需求创建配置文件,您可以使用 sumo-gui 打开并运行(同样可以双击或从控制台运行):
sumo-gui park05_cyber.sumocfg
请随意更改停车场的布局以及车辆的行为(速度、容量等)。每当您编辑 constants.py 或 createNetTaxi.py 时,请记住在运行场景之前重新执行 createNetTaxi.py。
调度算法描述#
待定
评估#
待定
与简单巴士的比较#
待定
