SUMO的停车场仿真
github地址:
https://github.com/JunXie-ZH/SUMO_Tutorial/tree/main/Parking
在SUMO中实现停车场仿真需要掌握三个部分的工作(如图1),分别为:
- 停车设施构建:在SUMO中停车设施的定义以及构建方式;
- 停车需求加载:灵活应用各种停车需求加载方式;
- 停车区重选择:车辆的目标车位被占时,如何找到新的停车位。
本章节将围绕这三部分工作对SUMO停车仿真进行详细介绍。
1 停车设施构建
1.1 停车区域与停车位
在SUMO中,停车设施由元素*<parkingArea>定义,该元素定义在.add.xml*文件中。
一个元素*<parkingArea>*详细定义如下:
<parkingArea id="parkingArea_B2C2_0_0" lane="B2C2_0" startPos="20.00" endPos="80.00" roadsideCapacity="5" width="3.50">
<space x="123.00" y="180.00"/>
<space x="128.00" y="180.00"/>
<space x="133.00" y="180.00"/>
<space x="138.00" y="180.00"/>
<space x="143.00" y="180.00"/>
<space x="152.00" y="184.00" angle="120.00"/>
<space x="160.00" y="184.00" angle="120.00"/>
<space x="168.00" y="184.00" angle="120.00"/>
<space x="176.00" y="184.00" angle="120.00"/>
<space x="184.00" y="184.00" angle="120.00"/>
</parkingArea>
元素*<parkingArea>*包含的各个属性定义:
属性 | 值类型 | 定义 |
---|---|---|
id | 字符串 | 停车区域ID,由用户自定义,且必须为唯一的 |
lane | 字符串 | 停车区域所在的车道,在SUMO中停车区域通常与车道是绑定在一起的 |
startPos | 浮点数 | 停车区域的起始位置,以车道起始点为标准,数值表示为距离车道起始点x米 |
endPos | 浮点数 | 停车区域的终止位置,以车道起始点为标准,数值表示为距离车道起始点x米,endPos>startPos |
friendlyPos | 布尔型变量 | 是否自动纠正无效的停车位置(默认为false) |
name | 字符串 | 对停车场的描述,可以用任意文字,仅用于可视化目的。 |
roadsideCapacity | 整型 | 该停车区域的路内停车位容量,默认值为0 |
onRoad | 布尔型变量 | 车辆停车时是否停留在路中,默认值为false,若设置为true,则只使用roadsideccapacity,不允许定义space给车辆停车 |
width | 浮点数 | 路内停车位的宽度 |
length | 浮点数 | 路内停车位的长度 |
angle | 浮点数 | 路边停车位的角度相对于车道角度,正值表示顺时针 |
从元素*<parkingArea>*所具备的属性,我们进行了如下的总结:
- ①*<parkingArea>所描述的是一个停车区域,包含多个停车位,且<parkingArea>*与路段车道(lane)是绑定的;
- ②可以定义任意停车位置和角度(angle),从而实现多种停车方式,如鱼骨式停车、平行停车等;
- ③*<parkingArea>包含了路内停车位(roadsideCapacity)与路外停车位(<sapce>),因此一个停车区域的总容量等于roadsideCapacity的数值与<sapce>*元素的个数之和。
对于③,在示例中,roadsideCapacity=5,*<sapce>*元素个数为10个,因此该停车区域的总容量为15个车位,在仿真中的可视化如图2:
停车设施的构建可以通过netedit编辑器实现,用户可以在已有道路路网的基础上,根据使用需求在合适的区域构建停车设施。
Step 1 用netedit编辑器打开路网文件(.net.xml),在Network模式下,点击上方工具栏[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5vxOmxzn-1643112936687)(./pic/addtionalMode.png)] 打开Additional mode(或点击上方菜单栏 Edit–>Additional mode打开)。在界面左侧Additionals中,点击Additional elements的下拉菜单选择parkingArea;
Step 2 完成上一步后,即可点击某个车道构建停车区域。在SUMO中,停车区域与车道是绑定的,表示为车辆需通过该车道进入该停车区域。一个车道上可以构建多个停车区域(图4.a),也可以只构建一个停车区域(图4.b),并可以在左侧的属性栏中的修改该停车区域的属性:如id,roadsideCapacity等,详细可见表1。
<additional xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://sumo.dlr.de/xsd/additional_file.xsd">
<parkingArea id="parkingArea_B2C2_0_0" lane="B2C2_0" startPos="20.00" endPos="80.00" roadsideCapacity="5" width="3.50" angle="45.00">
<space x="123.00" y="180.00"/>
<space x="128.00" y="180.00"/>
<space x="133.00" y="180.00"/>
<space x="138.00" y="180.00"/>
<space x="143.00" y="180.00"/>
<space x="192.00" y="184.00" angle="120.00"/>
<space x="160.00" y="184.00" angle="120.00"/>
<space x="168.00" y="184.00" angle="120.00"/>
<space x="176.00" y="184.00" angle="120.00"/>
<space x="184.00" y="184.00" angle="120.00"/>
</parkingArea>
</additional>
2 停车需求加载
停车是出行的其中一个环节,在前面的章节中,我们介绍了如何加载车辆的出行需求,停车需求的加载则是在出行需求的基础上进行实现的。停车需求的实战加载方式如图1所示主要有三种:直接在*.rou.xml文件中编写,通过netedit编辑器加载,通过TraCI*进行动态加载。
2.1 直接在*.rou.xml*文件中编写
首先可以直接在*.rou.xml文件中编写实现停车需求的加载。停车需求在.rou.xml*文件中的编写是在出行的基础上完成的,我们以一个出行(trip)为例:
我们在一个OD级的个体出行需求的基础上,添加了一个*<stop>元素,使其要停靠在一个parkingArea中。对于parkingArea*的选择:
- 可以选择终点路段的停车场(如上例中的parkingArea_e),则车辆行驶到终点路段后进入停车位停车,到停车时间(duration)后,车辆在终点路段行驶到尽头后消失在路网。
- 也可以选择在非起点和终点的停车场,但一定要在车辆的行驶路径中。但由于OD级的出行需求中,车辆的行驶路径是由SUMO决定的,停车场有可能并不在SUMO所决定的行驶路径中,运行仿真后会报如下错误:
以上均是个体级的停车需求在*.rou.xml*文件中的表示,对于集计型的交通需求(OD级与路径级)同样可以加载停车需求。
OD级:
<flow id="flow_0" begin="0" end= "7200" period="10" from="a" to="e">
<stop parkingArea="parkingArea_e" duration="100.00"/>
</flow>
路径级:
<route edges="a b c d e f g" color="yellow" id="route_0">
<stop parkingArea="parkingArea_d" duration="100.00"/>
</route>
<flow id="flow_0" begin="0" end= "7200" period="10" route="route_0"></flow>
2.2 通过netedit编辑器加载
netedit编辑器提供了可视化界面来进行停车需求的加载,但本质上还是为了生成一个*.rou.xml*文件来加载停车需求。
Step 1 如上所述,停车需求是在出行需求的基础上完成的,因此,首先在编辑器中加载好对应的出行需求,各粒度出行需求的编辑方式在前面已经介绍。假设已经创建了一个个体OD级出行需求,如下图:
Step 3 将停车需求的各项属性设置好后,点击车辆在出行过程中需要停留的停车区域(需要在出行路径上),即可给该需求添加上停车行为;
Step 4 点击保存后,打开保存好的*.rou.xml*文件,可以看到,相应的停车需求生成成功。
<routes xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://sumo.dlr.de/xsd/routes_file.xsd">
<trip id="vehicle_0" depart="0" from="a" to="e">
<stop parkingArea="parkingArea_e" duration="100.00"/>
</trip>
</routes>
2.3 通过TraCI动态加载
在实际的一些应用中,通过直接在*.rou.xml文件中编写和通过netedit编辑器来加载停车需求并不方便,这时候可以通过TraCI来动态加载出行需求和停车需求。TraCI动态加载需求的方式更加灵活,可以在每一个仿真步长中判断是否添加出行需求,且借助Python*的部分工具可以实现对车辆运动的精准监控。
我们以一个简单的例子来介绍如何通过TraCI动态加载停车需求。仿真时间一小时,每半分钟加载一个车辆出行需求,OD均为a–>e,并均在parkingArea_e停放100秒。应用的TraCI相关命令有traci.route.add、traci.vehicle.add以及traci.vehicle.setParkingAreaStop。
for step in range(3600): # 仿真一小时(3600s) if step % 30==0: # 每半分钟添加一个出行需求,并指定其停车位置 # 定义相关变量 tripid="trip"+str(step) # 出行ID origin="a" # 出发路段 destination="e" # 终点路段 vehid="v"+str(step) # 车辆ID parkingArea='parkingArea_e' # 停车区域
<span class="token comment"># 添加出行需求 & 停车需求</span>
3 停车区重选择
在停车场的实践过程中,经常会遇到这种情况:当车辆行驶到系统为其分配的停车区域时,发现已经无空闲的停车位。此时车辆是无法在该区域进行停车的,在这种情况下,车辆可以在道路上等待,直到该停车区域出现了可用的停车位。但是在现实场景中,司机会根据自己的需求与实际情况重新选择一个新的停车区域,因此,SUMO中也提供了让车辆重新选择一个停车区的功能。
针对停车区重选择行为,在SUMO中可以通过定义*<rerouter>中的<parkingAreaReroute>元素来实现。该元素中会定义一组可以相互用作替代的停车区,定义在.add.xml*文件中,示例如下:
<rerouter id="Rerouter_1" edges="a b c">
<interval begin="0" end="3600">
<parkingAreaReroute id="parkingArea_a"/>
<parkingAreaReroute id="parkingArea_b"/>
<parkingAreaReroute id="parkingArea_c"/>
</interval>
</rerouter>
*<rerouter>*的相关定义属性如下:
属性 | 值类型 | 定义 |
---|---|---|
id | 字符串 | 所定义的 rerouter的ID,用户自定义 |
edges | 浮点型 | 一个路段id或一个路段id列表,车辆在这些路段上可以触发rerouter |
probability | 浮点型 | 车辆触发rerouter的概率,默认为1 |
timeThreshold | 时间(秒) | rerouter触发时需与上一次触发时的间隔时间(默认为0) |
vTypes | 字符串列表 | 该rerouter适用的车辆类型,默认为适用所有车辆类型 |
off | 布尔型变量 | rerouter初始是否为不活动的,默认值:false |
<interval>的属性主要是begin: rerouter生效的开始时间,以及end: rerouter生效的结束时间。
*<parkingAreaReroute>*的各个属性含义如下表所示:
属性 | 值类型 | 定义 |
---|---|---|
id | 字符串 | 已经定义好的停车区域ID |
probability | 浮点型 | 每个可选方案(即备用停车区域被选中概率)被选中的概率(默认为1)。在重路由中,所有定义的概率都自动归一化。 |
visible | 布尔型 | 在车辆到达停车区域所在路段之前,是否知道该停车区的占用情况 |
通常而言,在以下两种情况时会触发停车的rerouter:
- 当车辆到达停车区域并且由于容量不足而无法停车时;
- 当*<parkingAreaReroute>元素具有属性 visible="true"时,车辆在没到达目标停车区域时就可知道目标停车区域的情况。若目标停车区域已满,且车辆位于<rerouter>元素中edge*属性中的路段时,车辆则会在备选停车区域中重新选择一个进行停车。
由于备选停车区域可能有多个,如上例,加入车辆初始的目标停车区域为parkingArea_a,若该停车区域已满,那么车辆则会根据一些因素的权重设置,选择parkingArea_b与parkingArea_c其中一个进行停车。相关的因素如下表:
属性 | 默认值 | 定义 | 值是否越大越好 |
---|---|---|---|
parking.probability.weight | 0 | parkingAreaReroute元素中所定义的probability | yes |
parking.capacity.weight | 0 | 备选停车区域的总容量 | yes |
parking.absfreespace.weight | 0 | 备选停车区域的绝对空闲车位数 | yes |
parking.relfreespace.weight | 0 | 备选停车区域的相对空闲车位数 | yes |
parking.distanceto.weight | 1 | 车辆到达备选停车区域的距离 | no |
parking.timeto.weight | 0 | 车辆到达备选停车区域所要花费的时间(假设值) | no |
parking.distancefrom.weight | 0 | 备选停车区域到车辆目的地的距离 | no |
parking.timefrom.weight | 0 | 备选停车区域到车辆目的地的行驶时间(假设值) | no |
SUMO会根据每个备选停车位的上述因子相对应的属性值,最终得出一个评估值 Pi=j=0∑nαjfj
若用户未对这部分因子进行设置,则默认parking.distanceto.weight因子的权重为1,其余因子的权重均为0,即可只有parking.distanceto.weight生效。另外需要注意得是,当*<parkingAreaReroute>*元素具有属性 visible="true"时,因子的值均是准确的;若visible=“false”,各项因子的值均为一个随机数,即便一个备选停车区域停满了也很有可能被选中作为重新选择的停车区域。
用户定义这些因子的权重,可以在*<vehicle>元素或<vType>*元素中定义:
<vehicle id="v0" route="route0" depart="0">
<param key="parking.capacity.weight" value="0.5"/>
<param key="parking.timeto.weight" value="0.1"/>
......
</vehicle>
<vType id="veh_1" width="1.5" length="3.50" maneuverAngleTimes="10 3.0 4.0,80 1.6 11.0,110 11.0 2.0,170 8.1 3.0,181 3.0 4.0"> <param key="parking.distanceto.weight" value="0"/> <param key="parking.distancefrom.weight" value="0.2"/> <param key="parking.absfreespace.weight" value="0.8"/> ...... </vType>
4 停车场景仿真实例
在本章节的实例中,我们来实现一个简单的智能停车场管理系统。
4.1 路网与停车设施构建
我们首先构建如图11所示的停车场路网,出入口分别有两个。
图11 停车场路网
在该路网的基础上,在部分路段中构建停车区域,如图11所示:
图12 停车场设施构建
4.2 停车场状态感知模块
本场景中,分别实现了两种程度的停车场状态感知模块:半感知与全感知。
- 半感知:仅感知当前停车场内所有区域的占用状态;
- 全感知:能感知当前停车场内所有区域的占用状态以及车辆的停车动向。
(1)半感知
class Half_perception:
# 感知停车场桩体,以“停车区域--占有率”表示
def Perceived_occupancy(self):
# 获取所有停车区域的ID
ParkingAreaList=traci.parkingarea.getIDList()
# 遍历所有停车区域,获取每一个区域的占有率
Occ=list(map(lambda x: float(traci.simulation.getParameter(x,
"parkingArea.occupancy")), ParkingAreaList))
# 构建一个datafrmame来存储“停车区域--占有率”
ParkingOcc=pd.DataFrame({"ParkingArea":ParkingAreaList,"Occupancy":Occ})
return ParkingOcc
# 获取可用停车区域
def get_AvailableParking(self):
# 获取“停车区域--占有率”关系表
ParkingOcc=self.Perceived_occupancy()
# 获取占有率小于1的停车区域列表
AvailableParking=list(ParkingOcc[ParkingOcc["Occupancy"]< 1]["ParkingArea"])
return AvailableParking