みなさん、GBFS(General Bikeshare Feed Specification)を触っていますか?
え、まだ?
いやいや、それは時代に遅れてますよ!!
あ、GBFSとはなんぞやについては、@kumatira さんの記事に詳しいのでそちらを参照ください。
https://qiita.com/kumatira/items/f9229c21d9f0db3b5ae3
今日はまだGBFSを触ったことがないという方のために、PythonでGBFSを簡単に扱えるライブラリを紹介いたします。
紹介するライブラリは下記の2点です(PyPIでGBFSを検索して出てきたライブラリ)。
- gbfs-client 0.1.8
- bicidata 0.0.5
なおPythonでバイクシェアを扱うライブラリとして、pybikes、python-citybikesといったライブラリもありますが、これらはCitybikesというGBFSとはまた別のバイクシェアデータフォーマットを扱うライブラリです。
本記事では、OpenStreet株式会社(ハローサイクリング) / 公共交通オープンデータ協議会のバイクシェア関連情報(GBFS形式)をCC-BY4.0に従って利用しています。
https://ckan.odpt.org/dataset/c_bikeshare_gbfs-openstreet
gbfs-client
まずgbfs-clientから紹介します。
gbfs-clientはここ数年、メンテナンスが止まっています。
しかし、取得したjsonをそのまま使うだけという設計が功を奏して、GBFSがバージョンアップしても問題なく動作します。
GBFSがsystem.csvをやめるなど、根本的な仕様変更をしない限りは使えるのでないかと思います。
インストール
pip install gbfs-client
バイクシェアのデータの探し方
GBFSはその仕様に「GBFSでデータを作ったらシステムIDを申請して登録してください」と定めています。
GBFSの仕様は、githubで管理されており、リポジトリ中に登録されたシステムの一覧がCSVで含まれています。
gbfs-clientは、そのCSVを利用して、世界各国のバイクシェアを検索する、SystemDeiscoveryServiceを提供しています。
from gbfs.services import SystemDiscoveryService
sds = SystemDiscoveryService()
#システムIDの一覧
print(sds.system_ids)
#システム情報の一覧
print(sds.systems)
['careem_bike', 'bike_nordelta', 'bike_buenosaires', 'biketobike', 'nextbike_al', 'Link_Linz',
'nextbike_na', 'nextbike_ka', 'nextbike_la', 'nextbike_ta', 'nextbike_si', 'Link_Vienna', 'nextbike_vt',
'nextbike_wr', '11b14b88-3598-4d3c-821d-e6f2e37cd901', '11b14b88-3598-4d3c-821d-e6f2e37cd902',
'11b14b88-3598-4d3c-821d-e6f2e37cd903', '11b14b88-3598-4d3c-821d-e6f2e37cd904', '11b14b88-3598-4d3c-821d-e6f2e37cd909',
'greenbike_aruba', 'nextbike_bj', 'nextbike_ba', 'nextbike_bz', 'bird-antwerp', 'bluebike',
'donkey_antwerp', 'donkey_gh', 'donkey_kortrijk_leiedal', 'lime_antwerp', 'lime_brussels',(以下略)
[{'Country Code': 'AE', 'Name': 'Careem BIKE', 'Location': 'Dubai, AE', 'System ID': 'careem_bike',
'URL': 'https://www.careem.com/en-ae/careem-bike/',
'Auto-Discovery URL': 'https://dubai.publicbikesystem.net/customer/gbfs/v2/gbfs.json',
'Validation Report': 'https://gbfs-validator.netlify.app/?url=https%3A%2F%2Fdubai.publicbikesystem.net%2Fcustomer%2Fgbfs%2Fv2%2Fgbfs.json'},
{'Country Code': 'AR', 'Name': 'Bike Nordelta', 'Location': 'Buenos Aires, AR', 'System ID': 'bike_nordelta',
'URL': 'https://bikeitau.com.br/nordelta/',
'Auto-Discovery URL': 'https://nordelta.publicbikesystem.net/ube/gbfs/v1/',
'Validation Report': 'https://gbfs-validator.netlify.app/?url=https%3A%2F%2Fnordelta.publicbikesystem.net%2Fube%2Fgbfs%2Fv1%2F'},
{'Country Code': 'AR', 'Name': 'Ecobici', 'Location': 'Buenos Aires, AR', 'System ID': 'bike_buenosaires',
'URL': 'https://www.buenosaires.gob.ar/ecobici',
'Auto-Discovery URL': 'https://buenosaires.publicbikesystem.net/ube/gbfs/v1/',
'Validation Report': 'https://gbfs-validator.netlify.app/?url=https%3A%2F%2Fbuenosaires.publicbikesystem.net%2Fube%2Fgbfs%2Fv1%2F'},
(以下略)
ご覧の通り、SystemDiscoveryService.system_ids, systems共にただのリストなので、イテレータを回して、条件を設定して任意のシステムのみ表示させることが可能です。
#docomoを含むIDのみ表示
for _id in sds.system_ids:
if 'docomo' in _id:
print(_id)
docomo-cycle-tokyo
#日本で運営されているシステムを抽出
jp_systems = [system for system in sds.systems if 'JP' in system['Country Code']]
print(jp_systems)
[{'Country Code': 'JP', 'Name': 'docomo bike share service', 'Location': 'Tokyo, JP', 'System ID': 'docomo-cycle-tokyo',
'URL': 'https://docomo-cycle.jp/',
'Auto-Discovery URL': 'https://api-public.odpt.org/api/v4/gbfs/docomo-cycle-tokyo/gbfs.json',
'Validation Report': 'https://gbfs-validator.netlify.app/?url=https%3A%2F%2Fapi-public.odpt.org%2Fapi%2Fv4%2Fgbfs%2Fdocomo-cycle-tokyo%2Fgbfs.json'},
{'Country Code': 'JP', 'Name': 'HELLO CYCLING', 'Location': 'JP', 'System ID': 'hellocycling',
'URL': 'https://www.hellocycling.jp',
'Auto-Discovery URL': 'https://api-public.odpt.org/api/v4/gbfs/hellocycling/gbfs.json',
'Validation Report': 'https://gbfs-validator.netlify.app/?url=https%3A%2F%2Fapi-public.odpt.org%2Fapi%2Fv4%2Fgbfs%2Fhellocycling%2Fgbfs.json'}]
SystemDiscoveryService.systemsは、GBFSのsystems.csvのフィールド名をkeyとした辞書になっているので、フィールドを使って様々な検索条件を作れます。
システムIDが既知の場合は、システムIDを直接指定してシステム情報を取ることも可能です。
#システムIDを指定してシステム情報を取得
system = sds.get_system_by_id('hellocycling')
print(system)
{'Country Code': 'JP', 'Name': 'HELLO CYCLING', 'Location': 'JP', 'System ID': 'hellocycling',
'URL': 'https://www.hellocycling.jp',
'Auto-Discovery URL': 'https://api-public.odpt.org/api/v4/gbfs/hellocycling/gbfs.json',
'Validation Report': 'https://gbfs-validator.netlify.app/?url=https%3A%2F%2Fapi-public.odpt.org%2Fapi%2Fv4%2Fgbfs%2Fhellocycling%2Fgbfs.json'}
GBFSの情報を取得する
gbfs-clientを使って、GBFSを取得するには、まずclientを構築する必要があります。
clientの構築には2通りのやり方があります。
1.SystemDiscoveryServiceから生成する
SystemDiscoveryServiceにはシステムIDを指定して、クライアントを生成する機能がありますので、それを用います。
client = sds.instantiate_client('hellocycling')
print(client)
GBFSClient('https://api-public.odpt.org/api/v4/gbfs/hellocycling/gbfs.json', 'jp')
なおinstantiate_client(system_id, language=None)というAPIになっており、langageを省略した場合、'en'が自動で設定されます。
ドコモ・バイクシェアとHELLO CYCLINGは日本語でしかデータ提供していないので、'jp'の指定がないとエラーになります。
2.URLを直接指定してclientを生成する
GBFSのうち、gbfs.jsonのURLが分かっている場合(システム情報のAuto-Discovery URLに記載)、そのURLを直接指定してclientを生成することもできます。
from gbfs.client import GBFSClient
client = GBFSClient('https://api-public.odpt.org/api/v4/gbfs/hellocycling/gbfs.json', 'jp')
print(client)
GBFSClient('https://api-public.odpt.org/api/v4/gbfs/hellocycling/gbfs.json', 'jp')
こちらも同じくlanguageに'jp'を指定しないと、ドコモ・バイクシェアとHELLO CYCLINGともにエラーになります。
サポートしているGBFS情報を確認する
GBFSにはいくつかの情報の種類があり、そのうち何の情報をサポートしているかを確認します。
print(client.feed_names)
['system_information', 'station_information', 'vehicle_types', 'station_status']
GBFSを取得する
サポートしているGBFS情報のフィード名を指定すると、その情報が取得できます。
print(client.request_feed('vehicle_types'))
{'ttl': 60,
'data': {
'vehicle_types': [
{'name': 'hellocycling bicycle',
'form_factor': 'bicycle',
'wheel_count': 2,
'vehicle_assets':
{'icon_url': 'https://d1yl7kw204zjxn.cloudfront.net/gbfs/v2/public/images/YAMAHA_HELLO.png',
'icon_url_dark': 'https://d1yl7kw204zjxn.cloudfront.net/gbfs/v2/public/images/YAMAHA_HELLO.png',
'icon_last_modified': '2022-04-01'},
'propulsion_type': 'electric_assist',
'vehicle_type_id': '2',
'max_range_meters': 70000,
'return_constraint': 'any_station',
'max_permitted_speed': 25,
'default_reserve_time': 30}]},
'version': '2.3',
'last_updated': datetime.datetime(2022, 11, 7, 14, 12, 29)}
request_feed()で取得した情報は、辞書形式になっているので、非常に扱いやすいです。
for datum in client.request_feed('vehicle_types')['data']['vehicle_types']:
print(datum['wheel_count'])
2
他のフィードも同様のやり方で取得、扱うことができます。
bicidata
gbfs-clientがGBFSの取得に特化していたのに対し、bicidataはGBFSフレームワークを名乗る通り、GBFSを活用しやすい形で取得する機能が提供されています。
- Snapshot : ある一瞬のGBFSの状態を取得
- Archivers : SnapshotをDataFrame+xarrayで保存
- Reporters : 未実装。GBFSの解析機能を提供する機能のようです。サンプルデータとその解析プログラムがGithubのスクリプト例に含まれているだけでモジュール機能としては提供されていません
- Publishers : 未実装。GBFSの公開機能(サーバ機能)を実装しようとしていたようですが、これも提供されていません。
Githubを見ても最後の更新が数年前なので、おそらく未実装の機能が実装されることはないでしょう。
インストール
公式サイトでは、
「pip install bicidata
するとPyPIバージョンが使えるけど、pip install git+https://github.com/bicidata/bicidata.git
が最新版使えるからオススメ」(意訳)
と書いてありますが、おすすめの方法だとインストールしても動きません。
setup.pyを見ると、依存パッケージにbicidataとbicidata.servicesとだけ書かれています。
しかし、bicidata.servicesは、bicidata.commonに依存しています。おすすめのインストール方法だと、services収録の機能を用いたときに、bicidata.commonがありませんと怒られます。
なので、下記手順でインストールします。
git clone https://github.com/bicidata/bicidata.git & cd bicidata
適当なエディタでsetup.pyを開き、L.15を下記のように修正
- packages=['bicidata', 'bicidata.services'],
+ packages=['bicidata', 'bicidata.services', 'bicidata.apps', 'bicidata.common'],
pip install -e .
bicidataはgbfs-clientに依存しています。
もしbicidataをインストールしても動作しない場合、gbfs-clientをインストールしてみてください。
snapshotを取る
snapshotは、スクリプト実行時点のGBFSの状態を保存する機能です。
公式サイトには、python -m bicidata.services.snapshot
を実行すれば、スナップショットが取れるよと書いてあります。
実行すると、gbfsフォルダが作成され、その中にバルセロナバイクシェアシステムのGBFSのstation_statusが保存されます。
任意のGBFSを取得させたい場合、cloneしたフォルダにある.env.templateを.envにリネームして設定を書けと公式サイトに書いてあります。
間違いではないですが、日本では間違いになります。
一例で、HELLO CYCLINGのGBFSを取得するよう、.envを書いてみましょう。
GBFS_SRC_API = "https://api-public.odpt.org/api/v4/gbfs/hellocycling/gbfs.json"
GBFS_DST_API = "http://localhost:8000"
SNAPSHOT_SAMPLE_TIME = 60
SNAPSHOT_GBFS_FOLDER = "gbfs"
実行します。
python -m bicidata.services.snapshot
Traceback (most recent call last):
File "/Users/muto_jo/.pyenv/versions/anaconda3-2021.11/envs/gbfs/lib/python3.9/runpy.py", line 197, in _run_module_as_main
return _run_code(code, main_globals, None,
File "/Users/muto_jo/.pyenv/versions/anaconda3-2021.11/envs/gbfs/lib/python3.9/runpy.py", line 87, in _run_code
exec(code, run_globals)
File "/Users/muto_jo/Study/gbfs/bicidata/bicidata/services/snapshot.py", line 134, in <module>
GBFSResource(
File "/Users/muto_jo/Study/gbfs/bicidata/bicidata/common/__init__.py", line 30, in __init__
super(GBFSResource, self).__init__(url, language, RemoteGBFS())
File "/Users/muto_jo/.pyenv/versions/anaconda3-2021.11/envs/gbfs/lib/python3.9/site-packages/gbfs/client.py", line 38, in __init__
raise Exception('Language must be one of: {}'.format(','.join(languages)))
Exception: Language must be one of: jp
'jp'を使えと怒られます。先にgbfs-clientで説明したエラーですね。
さてbicidata/services/snapshot.py の__main__(L.129以降)をみてみます。
if __name__ == '__main__':
dotenv.load_dotenv()
snapshot = Snapshot(
GBFSResource(
url=os.environ.get("GBFS_SRC_API", "https://barcelona.publicbikesystem.net/ube/gbfs/v1/gbfs.json")
),
FileStorageSaver(
folder=Path(os.environ.get("SNAPSHOT_GBFS_FOLDER", "gbfs")),
gbfs_url=os.environ.get("GBFS_DST_API", "http://localhost:8000/gbfs"),
)
)
snapshot.run()
GBFSResourceに言語指定ができません。
言語指定ができるようsnapshot.pyを修正してもいいのですが、snapshotの実行サンプルスクリプトが公式サイトにあるので、それを参考にsnapshotを用いたサンプルを自分で実装します。
import time
from bicidata.services.snapshot import Snapshot, FileStorageSaver
from bicidata.common import GBFSResource
num_snapshots = 3
snapshot_sample_time = 60 # time in seconds
snapshot = Snapshot(
GBFSResource("https://api-public.odpt.org/api/v4/gbfs/hellocycling/gbfs.json", "jp"),
FileStorageSaver(),
)
for i in range(num_snapshots):
print('snapshot {} run'.format(i+1))
snapshot.run()
time.sleep(snapshot_sample_time)
実行すると、gbfsフォルダができています。
gbfs/
gbfs.json
snapshot.json
station_statu/
{timestamp}.json
gbfs.jsonをみてみます。
{
"last_updated": 1668665669,
"ttl": -1,
"data": {
"en": {
"feeds": [
{
"name": "system_information",
"url": "https://api-public.odpt.org/api/v4/gbfs/hellocycling/system_information.json"
},
{
"name": "station_information",
"url": "https://api-public.odpt.org/api/v4/gbfs/hellocycling/station_information.json"
},
{
"name": "vehicle_types",
"url": "https://api-public.odpt.org/api/v4/gbfs/hellocycling/vehicle_types.json"
},
{
"name": "station_status",
"url": "https://api-public.odpt.org/api/v4/gbfs/hellocycling/station_status.json"
},
{
"name": "station_status_snapshots",
"url": "http://localhost:8000/station_status/{timestamp}.json"
},
{
"name": "snapshots_information",
"url": "http://localhost:8000/snapshots.json"
}
]
}
}
}
gbfs.jsonに2つのフィードがたされています。station_status_snapshotsとsnapshots_informationですね。
おそらくこれは将来、Publisherを実装した暁には、取得したSnapshotをサービスするためのフィード名とURLのようです。なお、このURLは.envの設定で変更可能なようです。
snapshot.jsonの中身はこうなっています。
{
"last_updated": 1668665551,
"ttl": -1,
"data": {
"timestamps": [
1668665253,
1668665551,
1668665551
]
}
}
station_status/の中には、
- 1668665253.json
- 1668665551.json
が収録されていました。
1分ごと3回取得しましたが、2回目と3回目でタイムスタンプが変更なかったので、情報が更新された2回分のみ保存されました。
中身は通常のstation_statusなので割愛します。
archiverを動か…せない?
さて次に紹介する機能は、bicidata.services.archiverです。
公式サイトに記述はありませんが、これもpython -m bicidata.services.archiver
で実行可能で、.envによる設定も可能です。
しかし、例によってlanguage指定ができません。
あとこの機能、Publisherが前提になっているように思われます。
というのも、Archiverクラスの中に、_get_snapshot_information()など、Snapshotで取得・生成したjsonがGBFS配信サーバから取得できることを前提にした処理があります。
またArchiverクラスがapi_resourceの型として指定している、bicidata.common.GBFSResourceSnapshotsは、snapshots_informationを必須としています(取得先のサーバからそれが取れることを求めている)。
bicidata.apps.serverというモジュールもあり、これがそのサーバ機能なのかなと思ったのですが、これにはGBFSの配信する機能はなく、定期的にSnapshotを取るサーバを動かすプログラムになっています。
(やっぱりlanguage指定はできません)
archiver.pyの__main__では、"http://35.241.203.225/gbfs.json"
をGBFSの取得先に指定し、動作するようになっています。
が、このサーバ、現在動作していません。
おそらく元はPublisherとして、Snapshotで取得したGBFSを配信するサーバ機能を実装し、Archiverはそこからデータを取得、DataFrame+xarrayで保存するつもりだったのでは?と推察します。
bicidata.apps.serverを実行したり、Archiverクラスを実装したスクリプトを動作させたわけではありませんが、ソースを読む限り実行できなさそうなので、bicidataの紹介はこれで終わりたいと思います。
なおソースは簡潔ですので、bicidata.apps.serverを日本国内のGBFS向けに実行可能に修正したりは簡単です。
もし、興味があって動かした方がいれば、ぜひQiitaに投稿ください。
まとめ
pypiでgbfsを検索して、出てきたライブラリを実際にインストールして試してみました。
2022年12月現在では、gbfs-clientが使いやすく、動作も確実なので、おすすめです。
bicidataはGBFSのようなリアルタイムなデータを扱うのに、利便性の高い機能を提供しようとしていますが、未完成かつ実装見込みが薄いのが残念なところです。
そのほか、私の知らないPythonでGBFSを扱うライブラリがあるかもしれません。
ご存じの方はぜひQiitaに投稿ください。