2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

みなさん、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を用いたサンプルを自分で実装します。

snapshot_sample.py
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に投稿ください。

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?