GTFSデータで公共交通空白地域を可視化してみる

本記事は[国土交通データプラットフォームアドベントカレンダー](https://qiita.com/advent-calendar/2023/mlit-dpf)18日目の記事です。

### はじめに
みなさん　こんにちは

最近、ニュースでバスの運転手不足の問題についてよく見ます。

* [2023/9/20 NHK バス事業120年 2030年度に運転手3万6000人不足か 背景と今後は](https://www3.nhk.or.jp/news/html/20230920/k10014200391000.html)
* [2023/10/5 読売新聞 路線バス運転手が足りない…続々と減便・廃止、運転手確保に苦しみ広告で「このままではヤバいです](https://www.yomiuri.co.jp/national/20231005-OYT1T50101/)
* [2023/11/13 日本経済新聞 路線バス、運転手足りず廃止 関西は都市路線も](https://www.nikkei.com/telling/DGXZTS00007720S3A101C2000000/)
* [2023/12/14 朝日新聞デジタル バス23社「路線廃止を計画」　運転手不足「24年問題」アンケート](https://www.asahi.com/articles/ASRDG62T9RDGPPZB001.html)

バス路線が廃止されると公共交通空白地域が広がり、交通難民が増加すると言われています。
今回は国土交通DPFデータに連携されているGTFSのデータを活用し、公共交通空白地域を見ていきます。

### GTFSって？
ChatGPTに聞いてみました。

>GTFSは、公共交通機関の運行情報を共有するためのフォーマットです。これにより、アプリやウェブサイトでバスや電車の時刻表や経路を提供できます。公共交通機関の情報をコンピュータで利用しやすくすることで、効率的な移動やスケジュールの立て方ができるようになります。

だそうです。もっと知りたい！という方は以下をご確認ください。
* [一般社団法人日本バス情報協会 GTFS初任者講習会を開催しました（動画公開中）](https://www.busdata.or.jp/?p=661)

### GTFSデータの取得
今回もAPIを利用してデータを取得していきます。
最早お馴染みとなったデータを取得する関数を今回も使いまわします。
（データを取得する関数については[16日目の記事](https://qiita.com/dpf_hirometai/items/ab820ed6e64bcd573b83)をご覧ください）

送信するリクエストボディは次のような内容です。
データセットIDにGTFSレポジトリのIDを指定し、また今回は富山県のデータを指定しています。

```python
graphql_query = """
query {
  search(
    term: "",
    phraseMatch: true,
    first: 0,
    size: 3000,
    attributeFilter: {
      AND:[
        {
          attributeName: "DPF:dataset_id",
          is: "gtfs"
        },
        {
          attributeName: "DPF:prefecture_code",
          is: 16
        }
      ]
    }
  ) {
    totalNumber
    searchResults {
      title
      lat
      lon
      shape
    }
  }
}
"""
```


これまでと同様にリクエストを送信し、結果をDataFrameに格納します。

```python
data = send_request(query=graphql_query)
df: pd.DataFrame = pd.json_normalize(data["search"]["searchResults"])
df.head()
```

![gtfs_df (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3629590/58a45b78-6b45-5ec7-6b8e-7a0e03a54682.png)

### バス停データの抽出
バス停情報の```MULTIPOINT```と路線情報の```LINESTRING```が```FeatureCollection```として１つのレコードに格納されており、バス停の座標のみを取り出すのがなかなか手ごわい。。。

今回は以下のようにバス停情報を抽出しました。効率的な方法があれば是非ご教示ください！

まずFeatureCollectionをGeoDataFrameに変換します。

```python
import geopandas as gpd
from shapely.geometry import Point, MultiPoint

gdf_list = []

# 各行のFeatureCollectionからGeoDataFrameを作成し、リストに追加
for features in df["shape.features"]:
    gdf_list.append(gpd.GeoDataFrame.from_features(features))

# リストのGeoDataFrameを連結
gdf = pd.concat(gdf_list, ignore_index=True)

gdf.head()
```

![gdf_1 (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3629590/6ba8586f-369b-0a97-d0cb-bcc7ff27a837.png)

```title```が```stops```のレコードだけに絞り込み、路線のLINESTRINGを除きます。

```python
gdf = gdf[gdf_concat["title"]=="stops"]
gdf.head()
```

![gdf_2 (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3629590/db469440-07af-c223-98ab-a231b96614d0.png)

各レコードの```MULTIPOINT```を展開して座標を取り出します。

```python
geometry = gdf["geometry"].explode(index_parts=False)
geometry.head()
```

|    | geometry                     |
|---:|:-----------------------------|
|  0 | POINT (136.878041 36.558079) |
|  0 | POINT (136.873596 36.559238) |
|  0 | POINT (136.874134 36.559268) |
|  0 | POINT (136.871949 36.560099) |
|  0 | POINT (136.872045 36.560236) |

GeoDataFrameに変換します。

```python
bus_stops = gpd.GeoDataFrame(geometry=geometry.reset_index(drop=True), crs="EPSG:4326")
bus_stops.head()
```

|    | geometry                     |
|---:|:-----------------------------|
|  0 | POINT (136.878041 36.558079) |
|  1 | POINT (136.873596 36.559238) |
|  2 | POINT (136.874134 36.559268) |
|  3 | POINT (136.871949 36.560099) |
|  4 | POINT (136.872045 36.560236) |

ようやくバス停情報を取得することができました！

### 公共交通空白地域の確認

富山県内のバス停の座標を取得できたので、徒歩圏域としてバス停からの半径300mの円バッファを作成します。
GTFSの座標は緯度・経度の形式で度単位のためメートル単位の平面直角座標系に変換してからバッファを作成し、また度単位に戻しています。

```python
bus_stop_buffer = bus_stops.to_crs(6675).buffer(300).to_crs(4326)
bus_stop_buffer.head()
```

![buffer (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3629590/00899cfb-3248-f1f1-2195-35685ae3511d.png)

Web地図上で確認します。

```python
m = folium.Map(tiles="http://cyberjapandata.gsi.go.jp/xyz/pale/{z}/{x}/{y}.png", attr="地理院タイル")
folium.GeoJson(bus_stop_buffer, name="buffer").add_to(m)
m
```

![map (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3629590/99125af5-fc79-12d7-9a50-afe6f66322ae.png)

可視化することができました！
空白地域が多く見えますが鉄道駅を追加するとまた違う結果になると思います。

### まとめ
今回はGTFSのバスデータのみで単純な円バッファでの可視化となりましたが、国土交通DPFのデータを活用して公共交通空白地域の可視化に取り組みました。
道路ネットワークを考慮した等時間圏域(Isochrone Map)にしたり、65歳以上人口の地域メッシュデータと重ねてみたり、試したいことはまだまだあるのですが、時間が溶けていくので今回はここまでにします。。。

みなさまもぜひ国土交通DPFのデータを活用してみてください！
それではまた～