にゅーとぴあ

気が向いたら書きます。

芝浦学バスAPIとの邂逅

この記事はデジクリ Advent Calender 2024 19日目の記事です。デジクリは芝浦工業大学の創作サークルです。18日目の記事は19th 蓮田さんの「Adobe InDesignとIllustratorを併用して同人誌を作った話」です。

21stの newt です。デジクリでは「幹部(インフラ)」という役職をやっています。

当初は今年読んだ本の振り返りを書こうと思っていたのですが、あまりにもつまらない記事ができあがりそうになってしまったので断念しました。せっかくデジクリというサークルの記事として書くので、何かしら大学に関連した話を書きたいと考えていたところ、春先に学バスの時刻表を活用したWebアプリを公開したことを思い出したので、今日は大学が提供している学バスのAPIについて書こうと思います。

学バスAPIとは

学外の方向けに簡単な説明を加えておくと、芝浦工業大学では東大宮駅から大宮キャンパスを結ぶ学バスが、日曜・祝日を除き毎日運行されています。

bus.shibaura-it.ac.jp

時刻表は上記のページからいつでも確認することができます。

駅からキャンパスは徒歩20分程度なので、歩こうと思えば歩ける距離ではあるのですが、せっかく学バスが運行されているのであればなるべくこれを利用したいところです。

基本的に授業が始まる前や後の時間帯に多く運行されていますが、少し外れた時間に行くと運行本数はまばらなので、このように時刻表をいつでも確認できるようにしてくれているのはありがたいです。特にこの手の情報はPDFで公開されている例も多く、Webで確認できるようにしてくれているだけでも良心的なのですが、実はこの時刻表にはさらに嬉しいポイントが存在します。

ページを最下部までスクロールすると、こんな文章が。

大宮バス時刻表カレンダーデータ:カレンダーのデータをご利用になりたい方はこちら

開発者用と題した、学バスの運行情報をJSON形式で提供してくれているページを発見しました。

bus.shibaura-it.ac.jp

APIの仕様を見てみる

一体何のメリットがあってこのような機能を提供しているのかわかりませんが、こんなものを用意されてしまったら、開発者として何も作らないわけには行きません。早速このAPI*1を使って遊んでみることにしました。

ページにはリファレンスがありますが、これだけではいまいちイメージが沸かないため、実際のデータを見てみます。

http://bus.shibaura-it.ac.jp/db/bus_data.json

エンジニア御用達(?)のサイト、transformJSONを丸ごとぶち込み、TypeScriptの型を生成してもらいましょう。

export interface Root {
  update: string
  timesheet: Timesheet[]
  calendar: Calendar[]
  site_info: SiteInfo[]
}

export interface Timesheet {
  status: string
  edit_time: string
  up_time: string
  title: string
  ts_id: string
  back_color: string
  text1: string
  text2: string
  text3: string
  cal_text: string
  double_line: string
  list: List[]
}

export interface List {
  bus_left: BusLeft
  train_left: TrainLeft
  time: string
  train_right: TrainRight
  bus_right: BusRight
}

export interface BusLeft {
  num1: string
  memo1: string
  num2: string
  memo2: string
}

export interface TrainLeft {
  num1: string
  memo1: string
  num2: string
  memo2: string
}

export interface TrainRight {
  num1: string
  memo1: string
  num2: string
  memo2: string
}

export interface BusRight {
  num1: string
  memo1: string
  num2: string
  memo2: string
}

export interface Calendar {
  status: string
  edit_time: string
  up_time: string
  title: string
  year: string
  month: string
  text1: string
  list: List2[]
}

export interface List2 {
  day: string
  ts_id: string
  comment: string
}

export interface SiteInfo {
  status: string
  up_time: string
  title: string
  info_view: string
  info_title: string
  info_text: string
  will: Will
}

export interface Will {
  up: Up[]
  down: Down[]
}

export interface Up {
  mark: string
  name: string
  tip: string
}

export interface Down {
  mark: string
  name: string
  tip: string
}

データを見ると、timesheetというものが複数存在し、calenderプロパティの中でどの日がどのtimesheetなのかを指定しています。

timesheet内はlistというプロパティが配列になっていて、一つの要素に1時間分のバスデータがあるようです。

例えば下のオブジェクトは9時台のデータを取り出したもので、bus_leftが駅前発のバス、bus_rightが校舎発のバスです。

{
  "bus_left": {
    "num1": "",
    "memo1": "9:16まで適時運行",
    "num2": "20.24.32.38.45.59",
    "memo2": ""
  },
  "train_left": {
    "num1": "00.a13.17.a21.29.35.b42.a48.a56",
    "memo1": "",
    "num2": "",
    "memo2": ""
  },
  "time": "9",
  "train_right": {
    "num1": "a10.d19.29.a36.d48",
    "memo1": "",
    "num2": "",
    "memo2": ""
  },
  "bus_right": {
    "num1": "",
    "memo1": "9:10まで適時運行",
    "num2": "14.18.26.32.39.53",
    "memo2": ""
  }
},

ここらへんからデータがキモい形をしていることに気づき始めるのですが、「〇〇分」にあたるデータがnum1とnum2にドット区切りで格納されています。せっかくJSONで構造化してたのに、何でここで文字列にしちゃうんですかね。

しかもこれ、時刻がnum1とnum2に分かれていたり、num2にしか入っていないこともあったり。

今回trainのほうは扱わないので目を瞑りましたが、数字の前のアルファベットが宇都宮線の列車種別を表しているらしく、これまた扱いづらい。なぜここまでDXの悪いデータになっているんでしょうか。

実はこの形でレスポンスが帰ってくるのには理由があり、それはもう一度時刻表を確認すると分かります。

「2024年度 大宮キャンパス 学バス時刻表  東大宮駅ルート」という題がついた学バス時刻表のWebページのスクリーンショット
学バス時刻表

どうでしょう。「この時刻表を表示するための構造」と説明されれば、まあ何となく分からんでもない、みたいな気持ちになるんじゃないでしょうか。というわけでこのエンドポイントは、「時刻表表示するために作ったものを、せっかくだから外部にも公開しておこう」というモチベーションによるものと考えられます。

とはいえこのまま時刻表を作っても公式と同じものしか作れず、作品として面白くないので、データを整形します。

この学バスが厄介なのは「適時運行」という概念がある点で、レスポンス中のnum1やnum2を見ると、「〜より適時運行」「〜まで適時運行」といった文字列が確認できます。実はこの「適時運行」という文字列にも表記揺れがあり、以前は「間隔を狭めて運行」という表記だったのが、最近はtimesheetによって表記が異なるという、サイレントな変更が加えられています。

このエンドポイント自体がAPIを名乗っているわけではないですが、開発者向けにJSONを提供しているのであれば、文字列の部分一致で判断せざるを得ない構造をもたせるのはやめてほしいなぁ……。

これらの仕様に留意しつつ、その日学バスが出発する時刻を1次元の配列に変換する処理を書いてあげると、以下のコードのようになりました。

github.com

作ったもの

アクセスしたタイミングで次バスが来る時間を算出し、「東大宮駅発」と「大宮キャンパス発」のバスそれぞれがあと何分で来るかを表示するWebアプリケーションを開発しました。

sit-bus.vercel.app

簡単に技術スタックを書くと、Next.jsのApp RouterをVercelにホストしていて、スタイリングもTailwind CSSという、THE・王道の選定をしています。

唯一変わっている点としてはBunを採用していることですが、実装上は打つコマンドが変わるくらいで、特段の設定なども必要ないので、大して真新しいことをしているというわけでもないです。

基本的にはアクセスがあったタイミングで学バスのAPIを叩き、整形して表示しているので、やっていることとしてはプロキシのようなものなんですが、dayjsのタイムゾーン設定にちょっと詰まりました。

Vercelにはproject Settings内にFunction Regionという項目があり、ここで「Tokyo, Japan (Northeast) - hnd1」というふうに指定することができます。

この設定で当然タイムゾーンも日本時間になるものだと思っていたのですが、何も考えずにdayjsのコードを書いたところ、表示される時間がずれるという問題が発生しました。

いろんな記事を参考にしつつdayjsの設定をいじっていたところ、なんとか日本から見て正しい時間が表示されるように修正できたのですが、この問題がどうして起きるのか、なぜ解決したのかがいまいち理解できておらず……。タイムゾーンの問題、割とありがちなネタだと思いますが、どうにも頭がこんがらがるので難しい。

むすびにかえて

こちらのWebアプリ、3月末にTwitterで宣伝したっきりでしたが、最近久しぶりにGoogle Analyticsを確認したところ今でも週に20人程度からアクセスがあり、ありがたい限りです。

せっかく大学が公式にAPIを提供してくれているのですから、もうひとひねりしたものを作りたいとは思っているんですが、いかんせんアイディアが湧いてこない。何か思いついた方は絶賛募集中ですのでぜひ意見をください。

明日12/20(金)の記事は21st あだぷたーさんの「キャラコンもSAN値もよわよわな私がつよつよなアンダインを倒した話」です。お楽しみに!

*1:正確にはWeb APIの定義に当てはまるのか定かではありませんが、便宜上