PythonでGTFSをパースするライブラリgtfs-parser

## はじめに

この記事は公共交通に関するデータセット「GTFS」をパースするライブラリ`gtfs-parser`の紹介をします。

:::note
GTFSの説明は下記記事に任せ、割愛します。
:::

https://qiita.com/Kanahiro/items/976900c4e5542acfe29d

https://qiita.com/Kanahiro/items/140f50b7f7894f260fde

まず2021年の話ですが、GTFSを位置情報データへ変換するツールとして、`GTFS-GO`というQGISプラグインを開発しました（QGIS＝位置情報データを表示するGISソフトウェア）。多方面から反響をいただき、バージョンアップを重ねてきました。

https://github.com/MIERUNE/GTFS-GO

GTFS-GO開発当初から、GTFSをパースする部分はPythonモジュールとして内部的に切り出していました。とはいえ、リポジトリ内の単なる1フォルダであるため、GTFS-GO以外からは使いにくい状態でした。

今回、GTFSをパースする部分を`gtfs-parser`として、①別リポジトリに切り出し②CLIの引数等を整理し③PyPIに公開したため、紹介します。

## gtfs-parser

https://github.com/MIERUNE/gtfs-parser

https://pypi.org/project/gtfs-parser/

### インストール

```sh
# Python v3.7.1 or newer
pip install gtfs-parser
```

### 使い方

#### CLI

```sh
gtfs-parser <mode> <src> <dst>
# mode: parse or aggregate
# src: gtfs-zip or gtfs-directory
# dst: output directory, 変換結果がGeoJSON形式のファイルとして書き出されます。
```

```sh:parse
# simple
gtfs-parser parse gtfs.zip output_dir

# with options
gtfs-parser parse gtfs_dir output_dir --parse_ignoreshapes --parse_ignorenoroute
# --parse_ignoreshapes: shapes.txtを無視する
# --parse_ignorenoroute: routeが通らないstopを無視する
```

```sh: aggregate
# simple
gtfs-parser aggregate gtfs.zip output_dir

# 名寄せを行わないオプション
gtfs-parser aggregate gtfs.zip output_dir --aggregate_nounifystops
# --aggregate_nounifystops: stopの名寄せを行わない

# オプション
gtfs-parser aggregate gtfs.zip output_dir \
  --aggregate_yyyymmdd 20230706
  --aggregate_delimiter -
  --aggregate_begintime 123456
  --aggregate_endtime 234501
# --aggregate_yyyymmdd: 集計対象の日付 20230706 -> 2023-07-06
# --aggregate_delimiter: stopの名寄せ時の、stop_idの区切り文字指定
# --aggregate_begintime: 集計対象の時間範囲（開始時刻） 123456 -> 12:34:56
# --aggregate_endtime: 集計対象の時間範囲（終了時刻） 234501 -> 23:45:01
```

#### Python API

```python
from gtfs_parser.gtfs import GTFS
from gtfs_parser.parse import read_routes, read_stops, 
from gtfs_parser.aggregate import Aggregator

#gtfs_dir: path to gtfs-directory
gtfs = GTFS(gtfs_dir)

# parse
stops = read_stops(gtfs) # GeoJSON-Features
routes = read_routes(gtfs) 
# with options
# stops = read_stops(gtfs, ignore_no_route=True)
# routes = read_routes(gtfs, ignore_shapes=True)

# aggregate
aggregator = Aggregator(gtfs) # take costs on init
aggregated_stops = aggregator.read_interpolated_stops() # GeoJSON-Features
aggregated_routes = aggregator.read_route_frequency()
# with options
# aggregator = Aggregator(gtfs,　no_unify_stops=True)
# aggregator = Aggregator(
#     gtfs,
#     yyyymmdd="20200101",
#     begin_time="000000",
#     end_time="235959",
#     delimiter=",",
# )
```

### 課題

https://github.com/MIERUNE/gtfs-parser/issues/1

スイスの1.62GBのGTFSの処理に**218分**かかるので、これをベンチマークに速度改善を図りたい（これは極端なケースですが、速度改善はGTFS-GOでも課題になっている）。

## 終わりに

MITライセンスで公開しているので、ぜひ使ってみてフィードバックをください（きっと動かないGTFSデータもあると思います。また、あらゆるコントリビュートを歓迎します。特にドキュメントやテストが不足している状態です。

### 次のプラン

`gtfs-parser`を外部から使いたくなった動機は、GTFSを動的に変換するサーバーを書きたくなり、そこで使いたかったためです。GTFSの在処を渡すとGeoJSONで降ってくるサーバーがあったら面白そうですよね。動的に返すためにはそれなりの速度が求められるため、上記のIssueが重要なわけです（それでも1.62GBのGTFSを動的に変換するのは不可能ですが）。

※開発中の画面（[ニューヨークのGTFS](http://web.mta.info/developers/data/nyct/bus/google_transit_bronx.zip)を利用、©️OpenStreetMap Contributors.）
![スクリーンショット 2023-07-06 14.09.45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/73197/f266b436-4169-94c3-0232-a618135deb2f.png)

こちらもそのうち公開出来たら良いなと思います。