背景・目的
GTFS及びGTFS-Flexについて
ツールと方法論
DuckDBへのGTFSの取り込み
Kepker.glでの可視化
ちょっと複雑な例(国勢調査・デマンド履歴・経路探索エンジンの利用)
本資料: https://www.tesshy.com/odpt/Contest2025/Contest2025_flex.html: 前回から応用事例を追加
前回: https://tesshy.github.io/odpt/Contest2025/Contest2025.html: DuckDBとKepler.glの使い方メイン
参考: TRONSHOW2024での資料: https://tesshy.github.io/odpt/TRONSHOW2024/TRONSHOW2024.html: GTFS-RTの使い方記載
渡邊徹志(1984年生まれ・TRONと同い年、栃木県出身) @tesshy
2009-04 - 2015-03: 東京大学大学院学際情報学府学際情報学専攻総合分析情報学コース博士後期課程坂村・越塚研究室(単位取得満期退学)
2012-04 - 2018-07: YRPユビキタス・ネットワーキング研究所研究員
2018-07 - 2019-02: 無職
2019-02 - 2024-01: 株式会社MaaS Tech Japan CTO
2024-02 - 2024-03: 無職
2024-03 - 2025-02: 株式会社Vook 執行役員(技術担当)
2024-03 - 2024-04: 無職
2025-05 - : 株式会社team-7、複数社お手伝い、農業(主にデジタル化、ドライバーとして)
公共交通オープンデータチャレンジ2025にて 新たにGTFS-Flex準拠のデータとデマンドODのデータが公開された。 本セミナーでは GTFS-FlexとデマンドODデータを使用する方法を紹介し、その応用例を示す。
ヒント 1: よくある話
「何を実現したい」から「何が必要か」を考えましょう。手段が目的とならないように・・・。
得られるもの
DuckDB as the hub of data ingestions and transformation
Needham, M., Hunger, M., Simons, M. (2024). DuckDB in Action. Manning Publications.
https://duckdb.org/docs/installation
winget install DuckDB.cli
curl https://install.duckdb.org | sh
curl https://install.duckdb.org | sh
https://kepler.gl
# ヒアドキュメントでクエリを流し込む、便利。
duckdb << EOS
LOAD spatial;
COPY (
SELECT ST_Union_Agg(geom) AS area FROM( -- 生成されたバス停毎の勢圏ポリゴンを重なっているものでマージする
SELECT *, ST_Buffer(ST_Point(stop_lon, stop_lat), 0.003) AS geom, FROM read_csv('./stops.txt') -- ST_Bufferでバス停勢圏ポリゴンを生成する
)
) TO './stops_area.geojson' WITH (FORMAT gdal, DRIVER 'GeoJSON', LAYER_CREATION_OPTIONS 'WRITE_BBOX=YES', SRS 'EPSG:4326'); -- stop_area.geojsonとしてポリゴンを保存する。SRSは元データに合わせるべき
EOS
バス停とバス停勢圏
-- demandにstopsをJOINしてODを表現したSQL
SELECT current_schema() AS region, pickup_stops.stop_name AS pickup_stop_name, pickup_stops.stop_lat AS pickup_stop_lat, pickup_stops.stop_lon AS pickup_stop_lon, drop_off_stops.stop_name AS drop_off_stop_name, drop_off_stops.stop_lat AS drop_off_stop_lat, drop_off_stops.stop_lon AS drop_off_stop_lon, count FROM (SELECT pickup_stop_id, drop_off_stop_id, count(pickup_stop_id) AS count
FROM demand GROUP BY pickup_stop_id, drop_off_stop_id) AS demand_count
LEFT JOIN stops AS pickup_stops ON demand_count.pickup_stop_id = pickup_stops.stop_id
LEFT JOIN stops AS drop_off_stops ON demand_count.drop_off_stop_id = drop_off_stops.stop_id;
# od.csv出力用シェルスクリプト(SCHEMAを書き換えれば他の地域でも生成可能)
SCHEMA=tamamura && duckdb contest2025.duckdb -csv -c "USE ${SCHEMA}; SELECT current_schema() AS region, pickup_stops.stop_name AS pickup_stop_name, pickup_stops.stop_lat AS pickup_stop_lat, pickup_stops.stop_lon AS pickup_stop_lon, drop_off_stops.stop_name AS drop_off_stop_name, drop_off_stops.stop_lat AS drop_off_stop_lat, drop_off_stops.stop_lon AS drop_off_stop_lon, count FROM (SELECT pickup_stop_id, drop_off_stop_id, count(pickup_stop_id) AS count FROM demand GROUP BY pickup_stop_id, drop_off_stop_id) AS demand_count LEFT JOIN stops AS pickup_stops ON demand_count.pickup_stop_id = pickup_stops.stop_id LEFT JOIN stops AS drop_off_stops ON demand_count.drop_off_stop_id = drop_off_stops.stop_id;" > od.csv
OD可視化(Arc)
SELECT * FROM stops
LEFT JOIN ( SELECT pickup.pickup_stop_id AS stop_id, pickup.pickup_count, drop_off.drop_off_count FROM (SELECT pickup_stop_id, COUNT(*) AS pickup_count FROM demand GROUP BY pickup_stop_id) AS pickup
LEFT JOIN (SELECT drop_off_stop_id, COUNT(*) AS drop_off_count FROM demand GROUP BY drop_off_stop_id) AS drop_off
ON pickup.pickup_stop_id = drop_off.drop_off_stop_id) AS counts
ON stops.stop_id = counts.stop_id;
# stops_count.csv出力用シェルスクリプト
SCHEMA=tamamura && duckdb contest2025.duckdb -csv -c "USE ${SCHEMA}; SELECT current_schema() AS region, * FROM stops LEFT JOIN ( SELECT pickup.pickup_stop_id AS stop_id, pickup.pickup_count, drop_off.drop_off_count FROM (SELECT pickup_stop_id, COUNT(*) AS pickup_count FROM demand GROUP BY pickup_stop_id) AS pickup LEFT JOIN (SELECT drop_off_stop_id, COUNT(*) AS drop_off_count FROM demand GROUP BY drop_off_stop_id) AS drop_off ON pickup.pickup_stop_id = drop_off.drop_off_stop_id) AS counts ON stops.stop_id = counts.stop_id;" > stops_count.csv
乗車回数分布
誰が、いつ、何処から何処に(OD)、何で、(いくらで)移動したか
たまGOと永井バスのバス停勢圏
-- メッシュ内総人口はT001142001カラムに保存されているので、メッシュコードのポリゴンと人口をJOINしてGeoJSONとして保存する
duckdb << EOS
load spatial;
COPY (
SELECT mesh_5th.KEY_CODE, census.T001142001::INT AS population, geom
FROM (SELECT * FROM ST_Read('./QDDSWQ5439/MESH05439.shp')) AS mesh_5th
LEFT JOIN (SELECT * FROM read_csv('tblT001142Q5439_utf8.txt', header = true)) AS census
ON mesh_5th.KEY_CODE = census.KEY_CODE
) TO 'census.geojson' WITH (FORMAT GDAL, DRIVER 'GeoJSON', LAYER_CREATION_OPTIONS 'WRITE_BBOX=YES');
EOS
たまGOと永井バスとセンサスと.png
国土地理院最適化ベクトルタイル(標準地図風スタイル)の適用
国土地理院最適化ベクトルタイル(標準地図風スタイル)の適用::結果
-- 中心座標と徒歩距離を与えたらIsochroneを示すPolygonを返すマクロ
CREATE OR REPLACE MACRO get_isochrone_geom(lng, lat, dist := 0.3) AS (
SELECT ST_GeomFromGeoJSON((res->>'body')::JSON->'$.features[0].geometry')
FROM (
SELECT http_get(
'http://localhost:8002/isochrone',
headers => MAP { 'accept': 'application/json' },
params => MAP { 'json': '{"polygons": true, "denoise": 0.0, "generalize": 1.0 , "costing":"pedestrian","contours":[{"distance":' || dist || '}], "locations":[{"lat":' || lat || ',"lon":' || lng || '}]}' }
) AS res
)
);
-- Stopsに徒歩300mのPolygonをJOINして、GeoJSONとして出力
USE tamamura;
COPY (
SELECT ST_Union_Agg(geom) AS area FROM (
SELECT stop_lat AS lat, stop_lon AS lon, get_isochrone_geom(lon, lat) as geom FROM stops
)
) TO 'stops_isochrone.geojson' WITH (FORMAT gdal, DRIVER 'GeoJSON', LAYER_CREATION_OPTIONS 'WRITE_BBOX=YES', SRS 'EPSG:4326');
徒歩半径比較
MotisでFlexな経路探索
osm: kanto.osm.pbf
tiles:
profile: tiles-profiles/full.lua
db_size: 274877906944
flush_threshold: 100000
timetable:
first_day: TODAY
num_days: 365
railviz: true
with_shapes: true
adjust_footpaths: true
merge_dupes_intra_src: false
merge_dupes_inter_src: false
link_stop_distance: 100
update_interval: 60
http_timeout: 30
incremental_rt_update: false
use_osm_stop_coordinates: false
extend_missing_footpaths: false
max_footpath_length: 15
max_matching_distance: 25.000000
preprocess_max_matching_distance: 0.000000
datasets:
nagai:
path: nagai.zip
default_bikes_allowed: false
default_cars_allowed: false
extend_calendar: false
tamamuraGTFS-flex:
path: tamamura_GTFS-flex.zip
default_bikes_allowed: false
default_cars_allowed: false
extend_calendar: false
elevators: false
street_routing: true
osr_footpath: false
geocoding: true
reverse_geocoding: true
DuckDB: ローカルOLAP環境、GISデータも扱える、今後のデータ処理におけるデファクトはこれだと思ってる
Kepler.gl: Uberが開発したWebGLを使った高速な地理情報可視化環境
OpenTripPlanner: オープンソースの経路探索エンジン、GTFSを読み込んで経路探索ができる
Valhalla: Mapzenが開発した経路探索エンジン、OSMのデータを使って経路探索できる
GraphHopper: 歴史ある経路探索エンジン、GTFSを読み込んで経路探索ができる
gtfs-realtime-bindings: GTFS-RTを扱う時に使うライブラリ、これがデファクトだと思う(方法はいっぱいあるけど)
Visual Studio Code: Microsoftが開発した軽量なIDE、PythonやSQLの開発にも使える、最早デファクトエディタ(エディタ戦争の終焉)
Jupyter Lab: Pythonのインタラクティブな開発環境、データ分析にはよく使われる
Quarto: Markdownで記述したドキュメントを、PDFやHTML、docxに出力できる。内部でPandocを使用。このプレゼンもQuarto製、TeXの時代は終わったのかもしれない・・・
uv: Rustで実装された、Pythonパッケージ管理システム。最近venv環境構築にも対応したのでpipenvから乗り換えた(脱Lock地獄)
Polars: Rustで実装された高速なデータフレームライブラリ、PythonのPandasと互換性がある
GeoPandas: Pandasの拡張で、GISデータを扱うためのライブラリ
deck.gl: Uberが開発したWebGLを使った高速な地理情報可視化ライブラリ、Python Bindingもある
Pandas: Pythonのデータフレームライブラリ、ど定番。
Dusk: Pythonの分散処理ライブラリ、Pandasで処理を書いてしまいデータが大きすぎて処理できない場合の救世主
dbt: ETLツール、SQLでデータ変換を記述できる
dlt: Data Load Tool、データロードを管理するためのツール
Apache Superset: Airbnbが開発したBIツール、OSSのBIツールの中ではGISデータの扱いが比較的得意
Forsquare Studio: Kepler.glの高機能版
Databricks: Apache Sparkをベースにしたクラウド型データ基盤、データ分析環境。DuckDBで管理しきれなくなったらこちらに移行検討を(最初から検討した方がいいケースもあり)