CityMobil

<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.pycreateNetTaxi.py 时,请记住在运行场景之前重新执行 createNetTaxi.py

调度算法描述#

待定

评估#

待定

与简单巴士的比较#

待定