【コスパの神】HJCE iPad用ペン 2025年発売iPad/iPad Air対応 アップルペンシル 超急速充電 タッチペン スタイラスペン ピクセルレベルの精度と低いレイテンシー ipadペンシル対応 磁気吸着/傾き感知/パームリジェクション 2018年以降iPad/iPad Pro/iPad Air/iPad mini対応(ホワイト)
¥1,139 (2025年5月4日 13:15 GMT +09:00 時点 - 詳細はこちら価格および発送可能時期は表示された日付/時刻の時点のものであり、変更される場合があります。本商品の購入においては、購入の時点で当該の Amazon サイトに表示されている価格および発送可能時期の情報が適用されます。)
前回の記事(新潟県三条市のコミュニティバスと新潟県燕市のコミュニティバスと燕市周辺の越後交通の路線バスの1日の動きを可視化してみた)では、GTFSデータとGTFS-RT(リアルタイム)データを用いて、GTFSデータとGTFS-RT(リアルタイム)データを用いて、越後交通と新潟県燕市・三条市のコミュニティバスを組み合わせた1日の運行状況を可視化しました。
この可視化には、Webサービスである「Mobmap Web(リンク)」を使用していましたが、今後は施設の利用状況なども含めて、より柔軟に可視化できる方法を模索したいと考えています。そこで今回は、その第一歩として、前回と同様の可視化をPythonの地図描画ライブラリ「folium」を使って再現することに取り組みました。
-
三条市のコミュニティバスのデータについて
-
燕市のコミュニティバスのデータについて
-
越後交通の路線バスのデータ
- GTFSリアルタイム(GTFS-RT)データ:
2024年3月21日に取得した、越後交通のGTFS-RTデータ(vehicle_positions)。 なお、以下のサイトから5分毎にデータを取得しました
- GTFSリアルタイム(GTFS-RT)データ:
前回の記事(新潟県三条市のコミュニティバスと新潟県燕市のコミュニティバスと燕市周辺の越後交通の路線バスの1日の動きを可視化してみた)で作成した、三条市のコミュニティバスのデータと燕市コミュニティバスのデータと越後交通のGTFS-RTデータを一つにまとめたCSVを使用しました。
またそれぞれのデータが異なるバス事業者のデータであることを明示するため、燕市のコミュニティバスのデータはroute_id
を0
とし、越後交通のデータはroute_id
を1
とし、三条市のコミュニティバスのデータはroute_id
を2
とすることで、事業者間の識別をできるようにしました。
最終的なCSVファイルは以下のような形式になっています。
id,timestamp,latitude,longitude,route_id
10658301001,2025-03-21 06:58:00+09:00,37.66339075,138.8242534,0
...
49906,2025-03-21 06:23:48+09:00,37.61711121,138.8234711,1
...
20720101001,2025-03-21 07:20:00+09:00,37.63088791,138.973589,2
アニメーション化用のプログラムを以下に示します。前回作成したデータを1分間隔に丸め込み、位置情報を線形補間しました。なぜこのようなことをしたかと言いますと、Mobmap Webを使ったときと比べてバスの動きに滑らかさがなかったためです。
bus_folium.py
import pandas as pd
import folium
from folium.plugins import TimestampedGeoJson
# CSV読み込み
df = pd.read_csv('0321_echigo_tsubame_sanjo_gtfs_realtime_data.csv', parse_dates=['timestamp'])
# タイムゾーンを削除し、60秒間隔で時刻を丸める
df['timestamp'] = df['timestamp'].dt.tz_localize(None)
df['rounded_timestamp'] = df['timestamp'].dt.round('60s')
# 補間処理(丸め後)
interpolated_rows = []
for bus_id, group in df.groupby('id'):
group = group.drop_duplicates('rounded_timestamp').set_index('rounded_timestamp').sort_index()
group_resampled = group.reindex(pd.date_range(group.index.min(), group.index.max(), freq='60s'))
group_resampled['id'] = bus_id
group_resampled['route_id'] = group['route_id'].ffill()
group_resampled[['latitude', 'longitude']] = group_resampled[['latitude', 'longitude']].interpolate()
group_resampled = group_resampled.dropna(subset=['latitude', 'longitude', 'route_id'])
# interpolated_rows.append(group_resampled.reset_index().rename(columns={'index': 'timestamp'}))
interpolated_rows.append(group_resampled.reset_index().rename(columns={'index': 'rounded_timestamp'}))
df_interpolated = pd.concat(interpolated_rows).reset_index(drop=True)
# route_idごとの色設定
color_dict = {0.0: 'red', 1.0: 'blue', 2.0: 'yellow'}
# GeoJSON用データ展開
expanded_rows = []
for bus_id, group in df_interpolated.groupby('id'):
group = group.sort_values('timestamp').reset_index(drop=True)
for i in range(len(group)-1):
current_row = group.iloc[i].copy()
current_row['next_timestamp'] = group.iloc[i+1]['timestamp']
expanded_rows.append(current_row)
df_expanded = pd.DataFrame(expanded_rows)
# GeoJSON生成
def df_to_geojson(df):
features = []
for _, row in df.iterrows():
color = color_dict[row['route_id']]
feature = {
'type': 'Feature',
'geometry': {
'type': 'Point',
'coordinates': [row['longitude'], row['latitude']],
},
'properties': {
'times': [
row['timestamp'].strftime('%Y-%m-%dT%H:%M:%S'),
row['next_timestamp'].strftime('%Y-%m-%dT%H:%M:%S')
],
'icon': 'circle',
'iconstyle': {
'fillColor': color,
'color': color,
'fillOpacity': 0.8,
'stroke': 'true',
'radius': 6
},
'popup': (
f"バスID: {row['id']}
"
f"路線ID: {int(row['route_id'])}
"
f"時刻: {row['timestamp'].strftime('%Y-%m-%d %H:%M:%S')}"
)
}
}
features.append(feature)
return {'type': 'FeatureCollection', 'features': features}
geojson_data = df_to_geojson(df_expanded)
# 地図作成
m = folium.Map(location=[df['latitude'].mean(), df['longitude'].mean()], zoom_start=13)
# Foliumアニメーション設定
TimestampedGeoJson(
geojson_data,
period='PT1M', # 60秒間隔
add_last_point=False,
auto_play=True,
loop=False,
max_speed=120, # 2分を1秒に高速再生
loop_button=True,
date_options='YYYY/MM/DD HH:mm:ss',
time_slider_drag_update=True,
duration='PT1M'
).add_to(m)
# 凡例表示
legend_html = '''
"position: fixed; bottom: 50px; left: 30px; width: 120px; z-index:9999; background-color:white; padding:10px; border:2px solid grey; border-radius:6px;">
凡例
"background:red;width:10px;height:10px;border-radius:50%;display:inline-block;"> Route 0
"background:blue;width:10px;height:10px;border-radius:50%;display:inline-block;"> Route 1
"background:yellow;width:10px;height:10px;border-radius:50%;display:inline-block;"> Route 2
'''
m.get_root().html.add_child(folium.Element(legend_html))
# HTMLとして保存
m.save('bus_animation.html')
print("アニメーションHTML生成完了: bus_animation.html")
そして、新潟県県央地域のバスの1日の動きをパーティクルで表した動画を以下に示します。
燕市と三条市のコミュニティバスは時刻表通りに動いたらという前提の動きになっています。そして越後交通の路線バスは2025/3/21のGTFS-RTデータを処理した動きになっています。なお、可視化した動きは朝7時からの動きです。
なお、対象にしたバス路線は以下になります。各事業者ごとにパーティクルの色を分けています。
- 越後交通の路線バス:青色
- 燕市コミュニティバス:赤色
- 三条市コミュニティバス:黄色
色々と試してみたのですが、あまり滑らかな動きにはできませんでした。
今回は、foliumを用いて、GTFSデータとGTFS-RT(リアルタイム)データを組み合わせ、越後交通および新潟県燕市・三条市のコミュニティバスの1日の運行状況を可視化しました。
ただし、以前使用したMobmap Webと比べると、動きの滑らかさにはやや劣る結果となってしまいました。
今後は、補間方法を工夫するなどしてこの課題に取り組みつつ、foliumを活用したオープンデータの可視化にも引き続き取り組んでいきたいと考えています。
注:
この記事は、以下の著作物を改変して利用しています。
CC BY 2.1(https://creativecommons.org/licenses/by/2.1/jp/deed.ja)
CC BY 4.0 https://creativecommons.org/licenses/by/4.0/legalcode.ja
Views: 0