前記事では、OpenTripPlannerとQGISプラグインにより、GTFSに含まれるバスルートや停留所の簡単表示を試みたが、到達圏解析(isochrone)をしようとすると、OTPを1系に落とさなくてはならなかったり(現在のOTPバージョン2.9)なかなか厄介なハマり点が出てきたため、もう一つの解析エンジンConveyal社のR5を使ってみる。
1.必要ツール
1. Python(推奨:3.11系)
Python公式サイト:
Python Downloads
初めてPythonインストールなら、インストール時に Add Python to PATH にチェックを入れる。
※Windowsなら、直接3.11.9の実行型インストーラダウンロードでもよい
Python 3.11.9 Windows installer (64-bit)
※2つ以上のバージョンを共存させるなら、Add Python to PATH を入れずに c:\Python311 などのフォルダを作成し、そこにインストールする。(R5実行時に指定)
コマンドプロンプトで確認。 python --version
※D:ドライブにPython311フォルダを作成し、そこにインストールした場合の確認。
D:\Python311\python --version
2. Java(推奨:Java 17 LTS)
R5 は内部で Java を利用して動作する。
コマンドプロンプトで確認。 java -version
3. r5py 関連ライブラリ
コマンドプロンプトで以下を実行する。
pip install r5py geopandas pyogrio
※使用するPythonを明示したい場合は、
Ⅾ:\Python311\Scripts\pip install r5py geopandas pyogrio など
2. R5の作業フォルダ整備
作業用フォルダを (ここでは外部SSDに r5フォルダ作成)用意
(例えば)D:¥r5
この下に、kiyobusなどのフォルダを作成し、GTFSとOSMデータを配置。
同じフォルダに、出力用 output フォルダと キャッシュ用 cache フォルダなども作成しておくとよい。
3.解析用プログラムテンプレート
ファイル位置や解析したい場所情報、出力先などを決める必要がある。
make_iso.py ファイルの冒頭に下記テンプレートを配置しておくとよい(わかりやすい)。
- OSMファイル
- GTFSファイル
- 目的地(清瀬駅など)
- 出発地点
- 出発時刻
- 出力先
# ============================================
# R5 到達時間解析テンプレート
# きよバスgtfsである場所(Origin01)から清瀬駅まで
# Origin01の座標は、QGISとQGIS-GOから取得
# ============================================
import datetime
import geopandas as gpd
import r5py
from shapely.geometry import Point
# ============================================
# OSM / GTFS
# ============================================
OSM_FILE = r"D:/r5/kiyobus/kanto-260511.osm.pbf"
GTFS_FILE = r"D:/r5/kiyobus/kiyobus.gtfs.zip"
# ============================================
# 出力先
# ============================================
OUTPUT_FILE = r"D:/r5/kiyobus/output/travel_time.gpkg"
# ============================================
# 目的地(清瀬駅)
# ============================================
DEST_NAME = "KiyoseStation"
DEST_LON = 139.5265
DEST_LAT = 35.7857
# ============================================
# 出発地点
# ============================================
ORIGIN_NAME = "Origin01"
ORIGIN_LON = 139.497806
ORIGIN_LAT = 35.775772
# ============================================
# 出発時刻
# ============================================
DEPARTURE_YEAR = 2026
DEPARTURE_MONTH = 5
DEPARTURE_DAY = 19
DEPARTURE_HOUR = 8
DEPARTURE_MINUTE = 0
# ============================================
# 条件
# ============================================
MAX_TRAVEL_MINUTES = 60
TRANSPORT_MODES = ["WALK", "TRANSIT"]
# ============================================
# 歩行速度(m/s)
# ============================================
WALK_SPEED = 1.0
# ============================================
# R5 ネットワーク生成(冒頭に続く本体)
# ============================================
print("R5 network 作成中...")
network = r5py.TransportNetwork(
OSM_FILE,
[GTFS_FILE]
)
print("R5 network 作成完了")
# ============================================
# 目的地
# ============================================
destinations = gpd.GeoDataFrame(
{
"id": [DEST_NAME]
},
geometry=[
Point(DEST_LON, DEST_LAT)
],
crs="EPSG:4326"
)
# ============================================
# 出発地点
# ============================================
origins = gpd.GeoDataFrame(
{
"id": [ORIGIN_NAME]
},
geometry=[
Point(ORIGIN_LON, ORIGIN_LAT)
],
crs="EPSG:4326"
)
# ============================================
# 到達時間計算
# ============================================
print("到達時間計算中...")
travel_times = r5py.TravelTimeMatrix(
transport_network=network,
origins=origins,
destinations=destinations,
transport_modes=TRANSPORT_MODES,
departure=datetime.datetime(
DEPARTURE_YEAR,
DEPARTURE_MONTH,
DEPARTURE_DAY,
DEPARTURE_HOUR,
DEPARTURE_MINUTE
),
max_time=datetime.timedelta(
minutes=MAX_TRAVEL_MINUTES
)
)
# ============================================
# 結果表示
# ============================================
print(travel_times)
# ============================================
# 保存
# ============================================
travel_times.to_file(
OUTPUT_FILE,
driver="GPKG"
)
origins.to_file(
r"D:/r5/kiyobus/output/origins.gpkg",
driver="GPKG"
)
destinations.to_file(
r"D:/r5/kiyobus/output/destinations.gpkg",
driver="GPKG"
)
origins.to_file(
r"D:/r5/kiyobus/output/origins.gpkg",
driver="GPKG"
)
print("===================================")
print("出力完了")
print(OUTPUT_FILE)
print("===================================")
4.解析実行例
Pythonの実行
D:\Python311\python D:\r5\kiyobus\make_iso.py
※javaが見つからないなどのエラーが出た場合は、set JAVA_HOME=C:\java などで
一時的にJAVA_HOME の場所を設定
実行成功すると
出力完了
D:/r5/kiyobus/output/travel_time.gpkg
などで終了する。
出力されたパッケージを、QGISにドラッグ、またはレイヤー→レイヤー追加→ベクターレイヤ追加

上図は、平日8時台に、青ひし形の位置から赤ひし形の位置まで移動するときに、何分発のコミュニティバスに乗るか、を表示したもの。タイミングに合わないと、歩くという結果も出せる。
5.到達圏表示
(1)データ準備
QGIS-GOにプリセットされている「北海道拓殖バス(一般路線バス・各町コミュニティバス)」を使う。ルートと停留所図は、下図のとおり。このデータを使って、帯広駅60分圏を表示する。

※gtfsデータは、(ユーザ名)\AppData\Local\Temp\GTFSGo から入手
r5フォルダ内に、hokkaidoフォルダ、その中に、OSMデータ(hokkaido)とgtfsデータと、outputフォルダを格納。(2)プログラムmake_hokkaido_iso.py
まず設定部分のみ(プログラム冒頭)
※太字部分は、各自のフォルダ構成によって変更すること# ============================================
# R5 北海道 到達圏解析
# 帯広駅 → 各地点
# 冬季・平日朝モデル
# ============================================
import datetime
import os
import geopandas as gpd
import pandas as pd
import r5py
from shapely.geometry import Point
# ============================================
# 入力データ
# ============================================
OSM_FILE = "D:/r5/hokkaido/hokkaido-260518.osm.pbf"
GTFS_FILE = "D:/r5/hokkaido/hokkaido_gtfs.zip"
# ============================================
# 出力先
# ============================================
OUTPUT_FILE = (
"D:/r5/hokkaido/output/"
"obihiro_travel_time_points.gpkg"
)
# ============================================
# 出発地(帯広駅)
# ============================================
ORIGIN_NAME = "ObihiroStation"
ORIGIN_LON = 143.2033288
ORIGIN_LAT = 42.9174467
# ============================================
# 出発時刻
# 冬季・月曜朝(データを持つ2023/1/16に設定)
# ============================================
DEPARTURE_YEAR = 2023
DEPARTURE_MONTH = 1
DEPARTURE_DAY = 16
DEPARTURE_HOUR = 8
DEPARTURE_MINUTE = 0
# ============================================
# 解析条件
# ============================================
MAX_TRAVEL_MINUTES = 120
TRANSPORT_MODES = ["WALK", "TRANSIT"]
# ============================================
# 冬季歩行モデル
# (積雪・凍結を想定)
# ============================================
WALK_SPEED = 0.7
MAX_WALK_MINUTES = 15
# ============================================
# メッシュ範囲
# (まずは帯広周辺)
# ============================================
MIN_LON = 142.85
MAX_LON = 143.45
MIN_LAT = 42.75
MAX_LAT = 43.05
# ============================================
# メッシュ間隔
# 約1km
# ============================================
GRID_STEP = 0.01
解析用メッシュを作る(今回は、1kmメッシュで)
# ============================================
# メッシュ生成
# ============================================
print("メッシュ生成中...")
destination_points = []
lon = MIN_LON
while lon <= MAX_LON:
lat = MIN_LAT
while lat <= MAX_LAT:
destination_points.append({
"id": f"{lon:.4f}_{lat:.4f}",
"geometry": Point(lon, lat)
})
lat += GRID_STEP
lon += GRID_STEP
# ============================================
# destinations GeoDataFrame
# ============================================
destinations = gpd.GeoDataFrame(
destination_points,
crs="EPSG:4326"
)
print(destinations.head())
print("destination数:", len(destinations))
(3)実行コマンド
D:\Python311\python.exe D:\r5\hokkaido\make_hokkaido_iso.py
実行すると、帯広駅周辺1kmメッシュを作成する
メッシュ生成中…
id geometry
0 142.8500_42.7500 POINT (142.85 42.75)
1 142.8500_42.7600 POINT (142.85 42.76)
2 142.8500_42.7700 POINT (142.85 42.77)
3 142.8500_42.7800 POINT (142.85 42.78)
4 142.8500_42.7900 POINT (142.85 42.79)
destination数: 1891
(4)プログラムの続き(追加)~Origin(帯広駅)作成
# ============================================
# Origin(帯広駅)
# ============================================
origins = gpd.GeoDataFrame(
{
"id": [ORIGIN_NAME]
},
geometry=[
Point(ORIGIN_LON, ORIGIN_LAT)
],
crs="EPSG:4326"
)
print(origins)
実行すると、設定したoriginが出力される
id geometry
0 ObihiroStation POINT (143.202 42.9178)
(5)ネットワーク計算(本体)追記~TravelTimeMatrix
# ============================================
# STEP 3: R5ネットワーク作成
# ============================================
print("===================================")
print("STEP 3: R5ネットワーク作成中...")
print("OSMとGTFSを読み込んでいます")
print("===================================")
network = r5py.TransportNetwork(
OSM_FILE,
[GTFS_FILE]
)
print("R5 network 作成完了")
# ============================================
# STEP 4: 到達時間計算
# ============================================
print("===================================")
print("STEP 4: 到達時間計算中...")
print("帯広駅から各メッシュ地点への所要時間を計算しています")
print("===================================")
travel_times = r5py.TravelTimeMatrix(
transport_network=network,
origins=origins,
destinations=destinations,
transport_modes=[
r5py.TransportMode.WALK,
r5py.TransportMode.TRANSIT
],
departure=datetime.datetime(
DEPARTURE_YEAR,
DEPARTURE_MONTH,
DEPARTURE_DAY,
DEPARTURE_HOUR,
DEPARTURE_MINUTE
),
max_time=datetime.timedelta(minutes=MAX_TRAVEL_MINUTES),
max_time_walking=datetime.timedelta(minutes=MAX_WALK_MINUTES)
)
print(travel_times.head())
print("===================================")
print("NaNでない地点数:")
print(travel_times["travel_time"].notna().sum())
print("===================================")
print("最小 travel_time:")
print(travel_times["travel_time"].min())
print("===================================")
print("NaNでない行サンプル:")
print(
travel_times[
travel_times["travel_time"].notna()
].head(20)
)
# ============================================
# STEP 5: geometryを結合
# ============================================
print("===================================")
print("STEP 5: 到達時間に地点geometryを結合中...")
print("===================================")
# TravelTimeMatrixの destination id と destinations の id を結合
result = destinations.merge(
travel_times,
left_on="id",
right_on="to_id",
how="left"
)
# ============================================
# NaN除外
# ============================================
result = result[
result["travel_time"].notna()
].copy()
print("===================================")
print("NaN除外後:")
print(len(result))
print(result.head())
# ============================================
# STEP 6: 出力
# ============================================
print("===================================")
print("STEP 6: GeoPackage出力中...")
print("===================================")
os.makedirs(os.path.dirname(OUTPUT_FILE), exist_ok=True)
result.to_file(
OUTPUT_FILE,
driver="GPKG"
)
print("===================================")
print("出力完了")
print(OUTPUT_FILE)
print("===================================")
(6)obihiro_travel_time_points.gpkg をQGIS表示
レイヤ追加 ベクターレイヤ追加
シンポロジ~Graduated にし、値はtravel_time 等間隔分類 クラス数6ぐらいで
赤 → 黄 → 緑 → 青 など

ラスタメニューから等高線を計算

