[SUMO] 地図データ上のEV走行シミュレーション
概要
交通シミュレーションSUMOでは、自ら定義した交通ネットワーク以外にも、OpenStreetMapの地図データをインポートして、実際の道路などに対するシミュレーションを実行することが可能です。
本記事では、以下のような内容について紹介したいと思います。
- SUMOの環境構築
- 地図データの読み込み方法
- 自動車シミュレーションの設定方法
- データの記録方法
- 簡単なデータの分析
セットアップ
Mac向けのセットアップ方法について解説します。SUMOと必要なライブラリをインストールし、設定を行います。
$ brew update
$ brew install --cask xquartz # まだインストールしていないなら
$ brew tap dlr-ts/sumo
$ brew install sumo
...
Don't forget to set your SUMO_HOME environment variable:
export SUMO_HOME="/usr/local/opt/sumo/share/sumo"
$ vi ~/.zshrc
...
export SUMO_HOME="/usr/local/opt/sumo/share/sumo" # 上記表示に従う
$ source ~/.zshrc
$ sumo --version
Eclipse SUMO sumo Version 1.9.2
実際の都市データをOSMから取り込む
SUMOに付属しているツールを起動し、OpenStreetMapの地図データをインポートしてみましょう。
$ Xquartz
$ cd $SUMO_HOME/tools
$ python osmWebWizard.py
上記を実行すると、webWizard(以下のスクリーンショット)がブラウザに表示されると思います。
試しにPositionのところでTokyoを検索してみましょう。
東京駅周辺の地図が表示されるかと思います。
次に、左上の-ボタンでズームアウトして、皇居周辺が収まるように調節してみましょう。
そして、「Generate Scenario」のボタンを押すことで、地図データのダウンロードが始まります。
あまり広い地図を指定し過ぎると、処理に時間がかかりますので、ズームアウトの際は注意が必要です。
処理が終了すると、以下のようなXquartzのアプリケーションとしてSUMOが起動すると思います。
交通シミュレーションの様子を見る
試しに緑色の再生ボタンを押して、交通シミュレーションを実行してみましょう。
地図上を走り回る小さな点で表現された乗用車を確認できたでしょうか。
なお、小さな点ではとても見づらいと思います。そこで、見やすくなるように、次の手順で設定変更することをオススメします。車の位置やブレーキ状態などの挙動が分かりやすくなると思います。
- メニューバーのEdit - Edit VisualizationからView Settingsを開く
- 「Draw with constant size when zoomed out」にチェック
- Minimum Sizeの数値を適当に変更
もし、GUIを終了してしまったら、以下のコマンドで再度SUMOを起動することが出来ます。
作業途中のものがある場合、そのsumocfgファイルを読み込ませましょう。
$ sumo-gui
EVを走らせてみる
任意の経路でEV自動車を走行させるための、追加設定について説明します。
webWizardを立ち上げたときのログや、SUMOのログエリアに、日付ディレクトリへのパスが表示されていると思うので、各種設定ファイルが置かれたそのディレクトリへ移動し、メインの設定ファイルを確認してみましょう。
$ cd /usr/local/Cellar/sumo/1.9.2_2/share/sumo/tools/XXXX-XX-XX-XX-XX-XX/
$ less osm.sumocfg
...
<configuration ...>
<input>
<net-file value="osm.net.xml"/> # 地図データ
<route-files value="osm.passenger.trips.xml"/> # 自動車などのデータ
<additional-files value="osm.poly.xml"/>
</input>
自動車に関する設定ファイルを確認してみます。
$ vi osm.passenger.trips.xml
...
<routes xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://sumo.dlr.de/xsd/routes_file.xsd">
<vType id="veh_passenger" vClass="passenger"/>
<trip id="veh0" type="veh_passenger" depart="0.00" departLane="best" from="538982924#12" to="-195531445#0"/>
<trip id="veh1" type="veh_passenger" depart="0.42" departLane="best" from="196380152#1" to="157249390"/>
<trip id="veh2" type="veh_passenger" depart="0.84" departLane="best" from="74314562#0" to="651707565"/>
<trip id="veh3" type="veh_passenger" depart="1.25" departLane="best" from="944555446#0" to="-39655751#1"/>
既に大量のvehXという定義が見つかると思います。
webWizardにおける自動車アイコンのところの設定に応じて、ランダムに生成された自動車データとなります。
いったん、既存の定義はコメントアウトして、最後にEVの定義を追加してみましょう。
$ vi osm.passenger.trips.xml
...
<!-- 【追加】
<trip id="veh0"...
...
<trip id="veh8616"...
--> 【追加】
...
<vType id="ev" emissionClass="Energy/unknown" color="white">
<param key="has.battery.device" value="true"/>
<param key="airDragCoefficient" value="0.35"/> <!-- https://www.evspecifications.com/en/model/e94fa0 -->
<param key="constantPowerIntake" value="100"/> <!-- observed summer levels -->
<param key="frontSurfaceArea" value="2.6"/> <!-- computed (ht-clearance) * width -->
<param key="internalMomentOfInertia" value="0.01"/> <!-- guesstimate -->
<param key="maximumBatteryCapacity" value="64000"/> <!-- 64kWh -->
<param key="maximumPower" value="150000"/> <!-- website as above -->
<param key="propulsionEfficiency" value=".98"/> <!-- guesstimate value providing closest match to observed -->
<param key="radialDragCoefficient" value="0.1"/> <!-- as above -->
<param key="recuperationEfficiency" value=".96"/> <!-- as above -->
<param key="rollDragCoefficient" value="0.01"/> <!-- as above -->
<param key="stoppingTreshold" value="0.1"/> <!-- as above -->
<param key="vehicleMass" value="1830"/> <!-- 1682kg curb wt + average 2 passengers / bags -->
</vType>
<trip id="ev1" type="ev" depart="0.00" departLane="best" from="出発地点のエッジID" via="通過地点1のエッジID 通過地点2のエッジID" to="到着地点のエッジID"/>
</routes>
ここで、各地点のエッジIDは、SUMO GUI上で道路を右クリックし、「Copy edge name to clipboard」するとコピーすることができます。車線などに注意して、所望の経路を辿るようにエッジを選んでいきましょう。
設定が済んだら、SUMO GUI上にて、再生ボタンの左の黄色のリロードボタンを押してから、再度実行ボタンを押すことにより、シミュレーションを行うことができます。
もしかすると、経路が見つからなかったりして、下部のメッセージエリアにエラーメッセージが出ているかもしれません。その場合は、エッジIDを選び直して、再度試してみてください。
うまく実行できた場合、画像のように、自らが選択した経路上を、EV自動車が走り回る様子を確認できるかと思います。
データの出力をさせる
分析などに使えるように、走行結果をデータとして保存してみましょう。
以下のように設定ファイルに、データ出力の設定を付け加えてみます。
$ vi osm.sumocfg
...
<output>
<battery-output value="battery.xml"/>
<battery-output.precision value="4"/>
<device.battery.probability value="1"/>
<summary-output value="summary.xml"/>
<fcd-output value="fcd.xml"/>
<fcd-output.geo value="true"/>
<tripinfo-output value="tripinfo.xml"/>
</output>
</configuration>
設定後、再度リロードボタンと実行を押して、シミュレーション終了後に出てくるダイアログのOKボタンを押すと、データが記録された各種xmlファイルが生成されているかと思います。
データを確認してみる
生成されたXMLデータを、扱いやすくなるように(?)、いったんCSVに変換してみます。
以下のPythonコードをparse.pyという名前で保存します。
from collections import defaultdict
import xml.etree.ElementTree as ET
import pandas as pd
battery = ET.parse("battery.xml")
fcd = ET.parse("fcd.xml")
tripinfos = ET.parse("tripinfo.xml")
all_vids = []
for tripinfo in tripinfos.findall("tripinfo"):
vid = tripinfo.get("id")
distance = tripinfo.get("routeLength")
all_vids.append(vid)
print(f"{vid}: {distance}m")
for vid in all_vids:
buf = defaultdict(list)
# battery
for timestep in battery.findall("timestep"):
time = timestep.get("time")
vehicle_element = timestep.findall(f"vehicle[@id='{vid}']")
if vehicle_element:
vehicle = vehicle_element[0]
energy = vehicle.get("energyConsumed")
soc = vehicle.get("actualBatteryCapacity")
else:
energy, soc = [None]*2
buf["time"].append(time)
buf["energy"].append(energy)
buf["soc"].append(soc)
# fcd
for timestep in fcd.findall("timestep"):
vehicle_element = timestep.findall(f"vehicle[@id='{vid}']")
if vehicle_element:
vehicle = vehicle_element[0]
longitude = vehicle.get("x")
latitude = vehicle.get("y")
angle = vehicle.get("angle")
speed = vehicle.get("speed")
slope = vehicle.get("slope")
else:
longitude, latitude, angle, speed, slope = [None]*5
buf["longitude"].append(longitude)
buf["latitude"].append(latitude)
buf["angle"].append(angle)
buf["speed"].append(speed)
buf["slope"].append(slope)
df = pd.DataFrame(buf)
df.to_csv(f"{vid}.csv", index=False)
以下のように実行すると、定義したEVごとの走行距離が表示されるのと同時に、csvが生成されるかと思います。
$ python parse.py
ev1: 8091.28m
CSVの中身を確認してみましょう。
$ less ev1.csv
time,energy,soc,longitude,latitude,angle,speed,slope
0.00,0.0000,32000.0000,139.769342,35.681020,26.090087,0.000000,0.000000
1.00,1.5461,31998.4539,139.769353,35.681039,26.090087,2.321455,0.000000
2.00,4.8279,31993.6260,139.769376,35.681078,26.090087,4.784444,0.000000
...
678.00,-10.0705,29660.8039,139.769470,35.681240,26.090087,28.332258,0.000000
679.00,19.1379,29641.6660,139.769619,35.681472,30.385849,29.041179,0.000000
680.00,,,,,,,
蓄電残量(soc)の最後の値(29641.6660)と、蓄電池の容量、先程表示された走行距離を用いて、燃費ならぬ電費を以下のように計算できます。
8091.28 / (32000 - 29641.6660) = 3.43 [km/kWh]
信号も多く、停車と発進を繰り返すためか、あまり良い電費とは言えないようです。
ぜひ、色々な地図や経路でも試してみてください。
最後にCSVデータの可視化を行います。
import pandas as pd
import matplotlib.pylab as plt
df = pd.read_csv("ev1.csv")
plt.figure(figsize=(20, 12), facecolor="white")
for ki, key in enumerate(["speed", "energy", "soc"]):
plt.subplot(3, 1, ki+1)
plt.plot(df[key].values)
plt.ylabel(key)
plt.xlim([0, len(df.index)])
plt.grid()
plt.show()
上記Pythonスクリプトを実行してみた結果が、次のグラフとなります。
上から順に速度、消費電力、蓄電残量となります。
停止が多い様子や停止時に一応回生ブレーキによる充電が行われている様子が分かります。
おわりに
今後普及していくであろうEV自動車について、実際の都市を例に走行シミュレーションを行い、簡単なデータ分析を行いました。
SUMOには、ここで紹介した以外にも、とても多くの機能が備わっており、例えば充電スポットを設定したり、高低差を考慮したり、さらに複雑なシミュレーションを行うことも可能です。
今後も面白そうな利用方法を思いつき次第、紹介していけたらと思っています。
Discussion