旅行と移動と出張と

このエントリは、平穏な生活を送りたいぴょこりんクラスタ Advent Calendar 2022のために書かれたものです。3つ目です。

まずは公理として、人は電車に乗ったり飛行機に乗ったらその記録をつけて残しておきたいもの、というものがあります。その方法は、人それぞれではありますが、やはりこの現代においては電子的な形で残したいものです。

そんなわけで今回は、今年自分で作った旅行記録システムの話です。自分用のシステムなのであまり得るものはないかもしれませんが、何かの参考になればと思っております。

旧システム

さて、記録を残すのが公理というのであれば、2022年の今になってそんなシステムを作るのはおかしいのではないかという意見もあるでしょう。 それは実にその通りでして、今まで下記のようなシステムを作成、運用してきました。

第1世代: Train Record Online (2012年~2016年)

これが最初のシステム化です。一番初期はメモ帳、次にテキストファイルという変遷を経て、ようやくWebシステムということになりました。

このシステムの機能は、以下のようなものです。

  • 日本の鉄道に乗った記録を残すことができる
    • 下記の情報を保存できる
      • 車両番号、乗車駅・降車駅、乗車プラットフォーム番号・降車プラットフォーム番号、発車日時・到着日時、列車種別、行先、列車番号、編成中の車両数、路線名、備考
      • 列車の時刻表上のデータ
    • 保存したデータの編集・削除・検索(駅名のみ)

まあ実にシンプルなものですが、これまでメモ帳あるいはテキストファイルに記録していた事項をベースにしたものです。

電子化したことによるメリットは、当然ながら、物理的な紙あるいはテキストファイルと違って、一つのところに保存できるので情報が散逸する可能性が少ないことです(もちろんサーバーが死んでしまうと一発アウトですが)。

またさらなるメリットとしては、外部から時刻表のデータをインポートして残すことができることです。どうやってインポートするのかについては、ソースが公開できない理由とともにお察しください。これによって、どんな列車に乗ったのかについて、単なる乗車区間以上の情報を残すことができます。もちろん、行先などいろいろな情報の入力を省力化することもできました。

ちなみにUIは下記のようなものです。(Firefoxの開発者モードで表示)

第1世代のスクリーンショット
第1世代のスクリーンショット

当時開発していたころには、確か今は亡きWindows Mobile機(WS011SHとか)での利用を想定していたため、PCのfirefoxでみるとちょっと狭い感じではあります。デザインセンスはないというのももちろんありますが。

実装では、下記のようなものを使っていました。いろいろと懐かしいですね。

項目 利用技術
バックエンド PHP + Smarty
DB Sqlite 3

第2世代: Integrated Transportation Record Online (2016年~2022年)

さて、前システムでしばらく満足していたのですが、2016年のころから前回の記事でも扱った修行を行うなど、飛行機の利用が増えてきました。このときまでは、飛行機については別のところ(Wiki)に保存していたのですが、公共交通機関の利用記録ということなので、同じシステム上にしてしまうのがいいのではないかということで、新たにシステムを作成するようにしました。何が"integrated"なのかというと、鉄道だけでなく、飛行機もついでにバスも扱えるようにしたところなのです。

このシステムの機能は、以下のようなものです。

  • 鉄道・飛行機・バスに乗った記録を残すことができる
    • 下記の情報を保存できる
      • 鉄道: 車両番号、号車番号・座席番号、座席種類、乗車駅・降車駅(ダイヤ)、発車日時・到着日時(ダイヤ)、列車種別・行先(ダイヤ)、列車番号、編成中の車両数、運行者、路線名、発車日時・到着日時(実際)、列車種別・行先(実際)、乗車プラットフォーム番号・降車プラットフォーム番号、備考、(日本の場合)時刻表のデータ
      • バス: 車両番号、登録番号、編成中の車両数、座席種類、号車番号・座席番号、乗車バス停・降車バス停(ダイヤ)、発車日時・到着日時(ダイヤ)、バス種別・行先(ダイヤ)、バス番号、運行者、路線名、発車日時・到着日時(実際)、バス種別・行先(実際)、備考
      • 飛行機: 機体モデル、登録番号、座席種類、座席番号、出発空港・到着空港(ダイヤ)、出発日時・到着日時(ダイヤ)、フライト番号、実際の運航会社のフライト番号、出発空港・到着空港(実際)、出発日時・到着日時(実際)、出発ターミナル・ゲート・スポット・滑走路、到着ターミナル・ゲート・スポット・滑走路、備考
    • 各日時には、国名とタイムゾーンを指定できる
    • 保存したデータの編集・削除・検索(出発・到着地名と車両番号・登録番号)

機能的な改善点としては、まず一番は飛行機・バスへの対応です。それぞれ入力できる項目も若干異なっています。第二に、出発地・到着地とその日時について、ダイヤ(計画)上のそれらと実際のそれらを分けて記録することができるというものです。これによって、自動的にそのときの遅れ・ダイバートなどなどについて記録することができます。第三は、タイムゾーンに対する対応です。特に飛行機に対応したことによって国をまたがる移動、日本国外の記録をする必要がでてきますので、出発地および到着地それぞれについて国名およびタイムゾーンを指定することができるようになっています。

こちらのUIは基本的に、第1世代を踏襲しているので、下記のような形です。絵文字を採用したのが、4年間の進化でしょうか。

第2世代のスクリーンショット
第2世代のスクリーンショット

実装としては、次のようなものを使っていました。

項目 利用技術
バックエンド Python 3 + Flask
DB Sqlite 3

ちなみに、DBMSとしては同じくsqlite3を使っていますが、DBを新しく作り直したため、第1世代のデータはすべてインポートされています。

システムのもたらすメリット

さて、今回のシステムの話に入る前に、こういった記録をつけたりシステムを作っておくと何がうれしいのかをもう少し書いていきましょう。

まずは、前回のエントリで書いた搭乗回数の遷移といったものが簡単に出せるということです。さすがにちょうどそんな統計データを出すようなUIは作っていませんでしたが、下記のようなSQL文で簡単に出すことができました。(ちなみに2016年以降しかないのは、搭乗記録が電子化されているのが第2世代の運用開始以降、すなわち2016年以降だからです)

SELECT substr(number, 1, 2) AS carrier, COUNT(*) FROM record WHERE transport_type = 3 AND substr(dep_datetime, 1, 4) = "2016" GROUP BY carrier;

あるいは、自分が使った回数の多い空港(2016年~2022年12月)はどこだろう?みたいな表も簡単に作ることができます。ちなみに、結果(10回以上)は、以下のようになりました。

空港名 発着回数
東京国際空港 (HND) 79回
サンノゼ国際空港 (SJC) 55回
那覇空港 (OKA) 53回
成田国際空港 (NRT) 52回
ロサンゼルス国際空港 (LAX) 19回
サンフランシスコ国際空港 (SFO) 18回
石垣空港 (ISG) 16回
ヒースロー空港 (LHR) 13回
ヘルシンキ空港 (HEL) 10回

さすがに住所地近くの空港は強いですね。まあ那覇空港が3位に入っているのは何かの間違いだと思いたいのですが。

もう一つの例は、自明ではありますが、旅行ネタでエントリを書く際の参考にできることです。日本帰国時の搭乗情報などがいい例でしょう。再掲すると以下のような感じです。

項目 内容
便名 NH 7
出発地 サンフランシスコ国際空港 (アメリカ) ゲートG10
到着地 成田国際空港 (日本) ゲート57A
予定 12:15 ⭢ 15:25 +1d
実績 12:21 ⭢ 14:46 +1d (10h27m)
機種・機体番号 B777-300ER (JA787A)

第2世代のところで述べたように、これに加えて使用した滑走路情報(SFOは28L, NRTは34L)なども入っています。

動機

さて、今回のエントリは、これらをさらに改良したシステム、つまり第3世代を作ることです。第2世代を作ってしばらく運用した後で出てきた追加要件は、下記のようなものです。

  • (RQ1) ほかの公共交通機関として、乗船(フェリーなど)も記録したい
  • (RQ2) 飛行機の記録として、獲得したポイント(前回の記事でも記述したFOPとかPPなど)を記録したい。また、座席の運賃クラスも記録したい
  • (RQ3) 旅行の事前計画もシステム化したい
  • (RQ4) 旅行のログとして、公共交通機関以外にも訪問した場所、宿泊地などについても記録したい
  • (RQ5) ログとコメントから、公開できる旅行レポートを自動生成したい
  • (RQ6) 写真やGPSログなどもまとめて管理したい

とこのように、要求項目がどんどん増えてきました。すべてに一気に対応するのは困難なので、まずは最初の4点について新システムで対応していこうと思います。

なお、新機能とは別に、

  • (RQ7) 第2世代のデータをインポートできること

も忘れてはいけません。システムを変えるたびに断絶が発生してしまったら意味がないですからね。

ちなみに、どうでもいいことですが、新システムの名前は、"Integrated Travel Record Online Extended"です。世代を追うごとにTの意味が拡大している(Train -> Transportation -> Travel)ことがわかるでしょう。

データ項目

いろいろと機能が増えますので、それぞれどんなデータ項目を残すべきかを考えます。

  • 計画に関するもの (RQ3, RQ4に関係)
    • 旅行計画
      • 旅行名、旅行期間
    • 移動計画
      • 移動手段種類、出発地、到着地、出発予定時刻、到着予定時刻、移動手段名、移動手段番号、費用
    • 宿泊計画
      • 宿泊期間、宿泊地名、住所、参照情報(URLなど)、費用
    • 訪問地計画
      • 訪問地名、出発予定時刻、到着予定時刻、住所、参照情報、費用
  • 記録に関するもの
    • 乗船記録 (RQ1に関係)
      • 船のモデル(?)、船体番号、船体登録番号、座席種類、座席番号、出港地・到着地(計画)、出港日時・到着日時(計画)、船便番号、運航者、路線名、備考
    • 飛行機記録 (追加分) (RQ2に関係)
      • 運賃クラス、航空会社プログラム種類、獲得ポイント数
    • 宿泊記録 (RQ4に関係)
      • (宿泊計画と同じ)
    • 訪問地記録 (RQ4に関係)
      • (訪問地計画と同じ)
    • メモ (RQ4に関係)
      • 時刻、コメント

という感じになるでしょうか。

スキーマ

まずはこれらを満たすためのデータ構造を考えます。これまでのシステムがリレーショナルデータベース(sqlite) を使っていたので、今回もリレーショナルデータベースを使うことにします。

第2世代のデータを引き継ぐことを考え、交通関係の記録については、第2世代の形を残しつつ、周りに新たに加わったデータ構造を付け加える感じとなりました。

まずは新機能となる、計画に関するものは記録とは独立したデータ構造としました。「旅行予定」という上位のテーブルに対して、複数の「移動計画」「宿泊計画」「訪問計画」がぶら下がる形です。ER図で書くと、以下のような感じです。

ER図 - その1
ER図 - その1

そのまんまですね。

次に、記録に関するものは、特に上位のものを設けないこととせず、移動・宿泊・訪問・メモの4種類のログがそれぞれ独立して存在する形としました。これは、計画の存在しないログが存在すること(通勤など日常生活や突発的な移動など)、一つのログは後々複数の旅行レポート(これはRQ5に関連し、今回の実装ではスコープ外)に結びつく可能性があるからです。後者の例としては、前回の修行エントリで言及した搭乗記録は、「JGP修行2022」というレポートとしても、それぞれの旅行、例えば「奄美大島旅行」というレポートとしても参照されるということになります。

そんなわけで、移動ログ(TransportationLogテーブル)は既存(第2世代)のスキーマに必要な情報を拡張した形とし、宿泊ログ・訪問ログは計画のそれと特に情報が増えないのでほぼそのまま、テキストログは非常にシンプルなテーブルとなりました。

ER図 - その2
ER図 - その2

このような形です。ただし移動ログについては、カラムが多いのでだいぶ省略してあります。 で、前述のとおり、ログは独立したものとしたので、リレーションはここには何もありません。

タイムゾーンの表現

ところで、第2世代以来悩んでいるのがタイムゾーンの表現方法です。第2世代では、次のような二つの要素で表していました。

カラム名 データ
時刻 (ISO 8601形式) 2022-12-25T00:00:00+0900
国コード JP

これからロジックで、オフセットは+09:00 (+540分)、国コードが日本であるから、タイムゾーンはJST、といった判別をしていました。

今回は、次のような4要素で表現することとしました。

カラム名 データ
時刻 (ISO 8601形式, UTC) 2022-12-24T15:00:00Z
国コード JP
タイムゾーン名 Asia/Tokyo
オフセット +540

これは主に、後述するようにDBMSにPostgreSQLを使うことにして、時刻を文字列ではなくDateTimeで扱うようにしたため、タイムゾーン情報が保持されないことによるものです(カラムのオプションにwith time zoneを加えると保持されますが、SQLの標準ではないので避けることとしました)。

実装

今回は、以下のようなライブラリや言語などを使いました。

項目 利用技術
バックエンド言語 TypeScript (Node.js)
Webフレームワーク Fastify
ORM Prisma
DBMS PostgreSQL
フロントエンド言語 Typescript
フロントエンドフレームワーク Vue 3
ビルドツール Vite
UIライブラリ PrimeVue

第1世代はPHP、第2世代はPython、第3世代はTypeScriptということで、当たり前ですが自分が主に使っている言語の変遷がそのまま見てとれます。

できたもの

こんな感じです。旅行計画は、下記のように表示されます。

第3世代のスクリーンショット (旅行計画)
第3世代のスクリーンショット (旅行計画)

ここでは、入力された移動計画と宿泊計画が表示されています。

また、移動の記録は、下記のような感じです(飛行機搭乗記録入力画面)。

第3世代のスクリーンショット (搭乗記録入力画面)
第3世代のスクリーンショット (搭乗記録入力画面)

という感じです。という以外はいいようがないですね・・・・

まとめ

このエントリでは、旅行の計画・記録システムとして作成したWebシステムの設計などについて述べてきました。実装については、特殊なこともないので、あまりおもしろみのある内容ではないですが、一つの特殊な用途に対するシステムの例として何かの参考になれば幸いです。

なお、このシステムの実運用についてですが、旅行計画については、今年の10月から運用を開始しました。旅行ログについては、特に第2世代でカバーしてきたところは移行がまだできていません。これは2023年1月をめどに行いたいと思っております。

電子化することによるメリットのところでも述べましたが、統計処理すればなにかおもしろい結果が得られるのではないかと、つい期待してしまいます。(無駄に膨れ上がったデータ処理に対するありがちな期待) 特に鉄道乗車記録は10年くらいのデータがあるわけで、なにか出ないですかねー。

Future Work

  • 時刻表サポートの拡充
    • オープンデータ、GTFSで提供されているものが、国内そして国際的にもいくつかあるのでこれをサポートしたいところです。そうすると例えばBARTの時刻表もこのシステム上で参照することができます。
  • 旅行レポートの作成
    • これが一番やりたかったことで、今年のアドベントカレンダーのネタにしたかったんですが、はて間に合うんでしょうかねえ。このエントリの公開当初はリンク切れだと思いますが、この二つ後の記事によって間に合ったかどうかがわかることでしょう。
  • GPSログとの連携
    • スマートウォッチやGPSロガーによってとったログも結構たくさんあるので、うまくこれを関係させたいところです。来年の開発はこれがメインテーマになりそうです。