GBFSデータ+MapLibre GL JSでシェアサイクルマップを作成する

# はじめに
:::note info
この記事は [シェアモビリティの標準的なデータフォーマット GBFS Advent Calendar 2023](https://qiita.com/advent-calendar/2023/gbfs) 12日目の記事です。
:::
[ドコモ・バイクシェアから日本全国のポートのデータがオープンデータ（CC BY 4.0）として公開された](https://ckan.odpt.org/dataset/c_bikeshare_gbfs-d-nationwide-bikeshare)ので、[ハローサイクリングのポートのデータ](https://ckan.odpt.org/dataset/c_bikeshare_gbfs-openstreet)とあわせて、[MapLibre GL JS](https://maplibre.org/maplibre-gl-js/docs/)+GBFSデータでシェアサイクルマップを作成してみました。

# デモサイト
https://shi-works.github.io/bikeshare-map-on-maplibre-gl-js/

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/205045/e92ee7b4-a0f5-a9d5-3ca0-a49580e0c58f.png)

# データの取得
- pythonで下記の公共交通オープンデータセンターよりGBFSデータ（station_information.json）を取得します。  
[ドコモ・バイクシェア](https://ckan.odpt.org/dataset/c_bikeshare_gbfs-d-nationwide-bikeshare/resource/addf55c2-d764-4d2c-9a89-f2a610663953)  
URL: `https://api-public.odpt.org/api/v4/gbfs/docomo-cycle/station_information.json`  
[HELLOCYCLING](https://ckan.odpt.org/dataset/c_bikeshare_gbfs-openstreet/resource/d45e9650-b243-4f5a-bda6-c2b0cb61e8a3)  
URL: `https://api-public.odpt.org/api/v4/gbfs/hellocycling/station_information.json`

```python:gbfs_station_dl.py
import json
import requests
import pandas as pd
from io import StringIO

# URLへアクセスしてデータを取得
url = requests.get("https://api-public.odpt.org/api/v4/gbfs/hellocycling/station_information.json")
# url = requests.get("https://api-public.odpt.org/api/v4/gbfs/docomo-cycle/station_information.json")

text = url.text

# 変換したいJSONファイルを読み込む
df = pd.read_json(StringIO(text))
print(df.head())

# ネスト（入れ子）構造になっている"data"という項目を展開する
df_json = pd.json_normalize(df['data'][0])

# JSONをCSVへ変換して文字コードをutf8で出力
df_json.to_csv(r"D:\交通データ\公共交通オープンデータセンター\シェアサイクルデータ\csv\hellocycling\hellocycling_station.csv",
               encoding='utf8', index=False)
'''
df_json.to_csv(r"D:\交通データ\公共交通オープンデータセンター\シェアサイクルデータ\csv\docomo-cycle\docomo-cycle_station.csv",
               encoding='utf8', index=False)  
'''
print('処理終了')
```

# 取得結果
- ドコモ・バイクシェア
`https://github.com/shi-works/bikeshare-map/blob/main/data/docomo-cycle_station.csv`
- HELLOCYCLING
`https://github.com/shi-works/bikeshare-map/blob/main/data/hellocycling_station.csv`  

# CSVからGeoJSONへの変換（QGIS）
- CSVからGeoJSONへの変換には[QGIS](https://www.qgis.org/ja/site/)を使用します。
- ドコモ・バイクシェア
`https://github.com/shi-works/bikeshare-map/blob/main/data/docomo-cycle_station.geojson`
- HELLOCYCLING
`https://github.com/shi-works/bikeshare-map/blob/main/data/hellocycling_station.geojson`  

# GeoJSONをMapLibre GL JSで表示する
- 上記の方法で生成したGeoJSONとMapLibre GL JSで作成したデモサイトとソースコードを下記のGitHubにて公開しています。

https://github.com/shi-works/bikeshare-map-on-maplibre-gl-js

## コードの解説
### GeoJSONの読み込み
- ` map.addSource`で`geojson`を読み込みます。
```javascript:javascript
            // ドコモ・バイクシェアソース
            map.addSource('docomo-cycle', {
                'type': 'geojson',
                'data': './data/docomo-cycle_station.geojson',
                attribution: '<a href="https://ckan.odpt.org/dataset/c_bikeshare_gbfs-d-nationwide-bikeshare">株式会社ドコモ・バイクシェア / 公共交通オープンデータ協議会</a>'
            });

            // （中略）
            
            // HELLOCYCLINGソース
            map.addSource('hellocycling', {
                'type': 'geojson',
                'data': './data/hellocycling_station.geojson',
                attribution: '<a href="https://ckan.odpt.org/dataset/c_bikeshare_gbfs-openstreet">OpenStreet株式会社 / 公共交通オープンデータ協議会</a>'
            });
```

### レイヤの表示（ポイント）
- `map.addLayer`でシェアサイクルのポイントデータをレイヤとして表示します。
- ポイントデータのレイヤは、ズームレベル9.5～23の間で表示されます。
- [グロー(発光)エフェクト](https://note.com/mapbox_japan/n/n631895b30a5f)を適用するために、レイヤーを3つ重ねています。
:::note info
「グロー(発光)エフェクト」とは、John M. Nelson氏が開発したカートグラフィー(地図製作)の手法のひとつで、マッピングしたデータを発光しているかのように表現するテクニックです。（引用元：Mapbox Japanの公式note）
:::
```javascript:javascript
            // HELLOCYCLINGポイントレイヤ
            map.addLayer({
                'id': 'hellocycling-points-1',
                'type': 'circle',
                'source': 'hellocycling',
                'minzoom': 9.5,
                'maxzoom': 23,
                'paint': {
                    'circle-color': 'rgba(249, 202, 34, 1)',
                    'circle-radius': 18,
                    'circle-blur': 2.5,
                    'circle-opacity': 0.6
                }
            });

            // HELLOCYCLINGポイントレイヤ
            map.addLayer({
                'id': 'hellocycling-points-2',
                'type': 'circle',
                'source': 'hellocycling',
                'minzoom': 9.5,
                'maxzoom': 23,
                'paint': {
                    'circle-color': 'rgba(249, 202, 34, 1)',
                    'circle-radius': 9,
                    'circle-blur': 1.5,
                    'circle-opacity': 0.8
                }
            });

            // HELLOCYCLINGポイントレイヤ
            map.addLayer({
                'id': 'hellocycling-points-3',
                'type': 'circle',
                'source': 'hellocycling',
                'minzoom': 9.5,
                'maxzoom': 23,
                'paint': {
                    'circle-color': 'rgba(255, 255, 255, 1)',
                    'circle-radius': 1,
                    'circle-blur': 0,
                    'circle-opacity': 1
                }
            });
```

### レイヤの表示（ヒートマップ）
- `map.addLayer`でシェアサイクルのポイントデータをヒートマップレイヤとして表示します。
- ヒートマップレイヤは、ズームレベル0～9.5の間で表示されます。

```javascript:javascript
            // HELLOCYCLINGヒートマップレイヤ
            map.addLayer({
                "id": "hellocycling-heatmap",
                "type": "heatmap",
                'source': 'hellocycling',
                'minzoom': 0,
                'maxzoom': 9.5,
                "paint": {
                    "heatmap-color": [
                        "interpolate",
                        [
                            "linear"
                        ],
                        [
                            "heatmap-density"
                        ],
                        0,
                        "rgba(255, 255, 255, 0)",
                        1,
                        "rgba(249, 202, 34, 0.5)"
                    ],
                    "heatmap-radius": [
                        "interpolate",
                        [
                            "exponential",
                            2
                        ],
                        [
                            "zoom"
                        ],
                        2,
                        8,
                        9,
                        20
                    ]
                }
            });
```

# おわりに
MapLibre GL JS+GBFSデータでシェアサイクルマップを作成する方法についてご紹介しました。最後に、MapLibre GL JS+GBFSデータを用いたWebマップの事例をいくつかご紹介します。

#### シェアサイクルと傾斜量図・シームレス標高タイル（3D地形）をMapLibre GL JSで表示するデモサイト
:::note info
傾斜量図とは：地表面の傾きの量を算出し、その大きさを白黒の濃淡で表現したものです。白いほど傾斜が緩やか、黒いほど急峻であることを意味します。
出典）https://www.gsi.go.jp/bousaichiri/slopemap.html
:::
https://shi-works.github.io/bikeshare-map-with-slopemap-on-maplibre-gl-js/#10/35.6527/139.7719

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/205045/8d6cabda-05ed-9cd6-9fb7-7f3aeef6dba6.png)

#### シェアサイクル満空情報マップ
:::note warn
貸出可能台数の割合は下記で算出していますが、これが満空情報を示すための正しい指標なのか定かではありません（有識者のご意見を伺いたい）。
貸出可能台数の割合=レンタル可能な自転車の数（num_bikes_available）÷（レンタル可能な自転車の数(num_bikes_available）+返却可能な自転車の数（num_docks_available））
:::
https://shi-works.github.io/bikeshare-demand-map/#10/35.7045/139.711

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/205045/c0b9b1ef-be06-4bb0-5c5a-29e4ccae8585.png)