【Bラボデジタルサイネージ13】直近2件のバス時刻表を表示する「BusRModule」

![](https://qiita-user-contents.imgix.net/https%3A%2F%2Fuser-images.githubusercontent.com%2F29194025%2F137117015-af05fe4f-7c32-4e38-876f-e153ff2f2f7d.png?ixlib=rb-4.0.0&auto=format&gif-q=60&q=75&w=1400&fit=max&s=2bbf46fb5c1d8dec16f2fcb5d089fde2)

https://github.com/ohayota/BLabo-digital-signage

本記事は、[株式会社 函館ラボラトリ](http://www.hakolab.co.jp)が運営する「[Bラボ](http://www.hakolab.co.jp/b-lab/index.html)」における、大人向け「看板アプリ（デジタルサイネージ）を作るコース」用教材テキストです。

http://www.hakolab.co.jp/b-lab/index.html

1. [【Bラボデジタルサイネージ1】Processing+ラズパイでデジタルサイネージを作る！](https://qiita.com/ohayota/items/f4aa3e1b452060eb52ad)
2. [【Bラボデジタルサイネージ2】ラズパイ初期設定](https://qiita.com/ohayota/items/88fbcf177e6606311efb)
3. [【Bラボデジタルサイネージ3】準備プログラム作成＆機能実現の方針決定](https://qiita.com/ohayota/items/41a746d2258673726541)
4. [【Bラボデジタルサイネージ4】レイアウトの基準になるグリッドを表示する「GridModule」](https://qiita.com/ohayota/items/df704f523b5aa4dbb180)
5. [【Bラボデジタルサイネージ5】レイアウトの基準になる枠を表示する「PlaceholderModule」](https://qiita.com/ohayota/items/f52a10660009794afd9f)
6. [【Bラボデジタルサイネージ6】画像を全画面表示する「FullImageModule」](https://qiita.com/ohayota/items/4a9c8423459c251f4911)
7. [【Bラボデジタルサイネージ7】ページの自動切り替え](https://qiita.com/ohayota/items/9d2c28f9cbf2f559be31)
8. [【Bラボデジタルサイネージ8】設置されている場所の名前を表示する「LocationModule」](https://qiita.com/ohayota/items/046b361d945a709c4605)
9. [【Bラボデジタルサイネージ9】現在の時間を表示する「DateModule」](https://qiita.com/ohayota/items/6b80137e33bfb4172de6)
10. [【Bラボデジタルサイネージ10】ページ切り替えの時間が分かる「ProgressBarModule」](https://qiita.com/ohayota/items/9f3b6f9f32b7d93ef2a7)
11. [【Bラボデジタルサイネージ11】現在の表示中のページが分かる「PageControlModule」](https://qiita.com/ohayota/items/23acc993dca47286fffb)
12. [【Bラボデジタルサイネージ12】現在の天気を表示する「WeatherRModule」](https://qiita.com/ohayota/items/d9b1d21037dcedcfd78c)
13. **【Bラボデジタルサイネージ13】直近2件のバス時刻表を表示する「BusRModule」（本記事）**
14. [【Bラボデジタルサイネージ14】ごみ出しカレンダーを表示する「GomiRModule」](https://qiita.com/ohayota/items/40ed5db352a688637f83)
15. [【Bラボデジタルサイネージ15】（発展編）ツイートを表示する「TwitterRModule」](https://qiita.com/ohayota/items/6367fad7beaf9ad9ea1c)
16. [【Bラボデジタルサイネージ16】開店／閉店を表示する「OpenCloseRModule」](https://qiita.com/ohayota/items/284b5c7e7101f602016f)
17. [【Bラボデジタルサイネージ17】部屋の温度を表示する「TemperatureRModule」](https://qiita.com/ohayota/items/386c27a021601fb3ccfd)
18. [【Bラボデジタルサイネージ18】部屋の明るさを表示する「BrightnessRModule」](https://qiita.com/ohayota/items/76279927b4ef35eaf628)
19. [【Bラボデジタルサイネージ19】起動画面を表示する「LaunchingScreenModule」](https://qiita.com/ohayota/items/0b6bdeda68c36707f762)
20. [【Bラボデジタルサイネージ20】RModuleの影を実装](https://qiita.com/ohayota/items/57242f18653ccf7eb83b)


# 前提

BusRModuleでは、バス時刻表のためのデータにGoogleスプレッドシートを使用します。
あらかじめGoogleアカウントを取得しておいてください。


# 作るもの

Googleスプレッドシート上に打ち込んだバス時刻表を使い、直近2件のバス発車時刻を表示します。

![0023.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/368192/9fdb7e0b-010e-049f-f417-7e36d1fae0b2.png)


# 画像の準備

BusRModuleの背景画像には、下の写真を使用します。

![background.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/368192/5cc83384-8fb9-c6c2-ba26-2d5212d983fd.jpeg)

画像を保存し、「background.jpg」という名前をつけます。
（.png形式など別の形式の場合は、.jpg形式に変換してください。）

バス停までのマップには、下の画像を使用します。

![bus_map.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/368192/39ad2807-061a-4e50-b3fd-106462c6f1f1.jpeg)

画像を保存し、「bus_map.jpg」という名前をつけます。
（.png形式など別の形式の場合は、.jpg形式に変換してください。）

Processingの画面上のタブで、「スケッチ > スケッチフォルダーを開く」を選ぶと、開いている.pdeファイルが一覧で表示されます。
「data」フォルダの中に、新たに「bus」フォルダを作ってください。
先ほど名前をつけて保存した「background.jpg」を「data/bus」フォルダの直下においてください。（ドラッグ＆ドロップ）

ここで新しく作った「data/bus」フォルダの位置を示すパスを、定数として定義します。

``` java:DigitalSignage.pde
/* 略 */

final String AD_PATH = "ad/";
final String WEATHER_PATH = "weather/";
final String BUS_PATH = "bus/"; /* 追加 */

/* 略 */
```

`bus/bus_map.jpg`をプログラムで読み込みます。
BusRModuleの背景を保持する変数も定義します。

``` java:DigitalSignage.pde
/* 略 */

/* ここから追加 */
// BusRModule
PImage busMap;
PGraphics busBackground;
/* ここまで追加 */

/* 略 */
```

`initializeImage()`の中で`bus_map.jpg`を読み込みます。

``` java:DigitalSignage.pde
void initializeImage() {
  busMap = pImageCut(loadImage(BUS_PATH + "bus_map.jpg"), CENTER, CENTER, 1280, 720); /* 追加 */
  adImage = new PImage[AD_IMAGE_COUNT];
  for (int i = 0; i < AD_IMAGE_COUNT; i++) {
    adImage[i] = loadImage(AD_PATH + "ad" + i + ".jpg");
  }
}
```

次にモジュール背景を生成しておきます。
WeatherRModuleのときと同様に、`initializeRModuleBackground()`内で実装します。

``` java:Initialize.pde
/* 略 */

void initializeRModuleBackground() {
  RModule module;
  int w;
  int h;
  PImage back;
  
  module = RModule.Weather;
  w = moduleWidth( moduleSize(module) );
  h = moduleHeight( moduleSize(module) );
  back = loadImage(WEATHER_PATH + "background.jpg");
  weatherBackground = createGraphics(w, h);
  weatherBackground.beginDraw();
  weatherBackground.colorMode(HSB, 360, 100, 100, 100);
  weatherBackground.image( pImageCut(back, CENTER, CENTER, w, h) , 0, 0);
  weatherBackground.fill(0, 0, 0, 40);
  weatherBackground.noStroke();
  weatherBackground.rect(0, 0, w, h);
  weatherBackground.endDraw();
  weatherBackground.mask( sizeToModuleMask( moduleSize(module) ) );
  
  /* ここから追加 */
  module = RModule.Bus;
  w = moduleWidth( moduleSize(module) );
  h = moduleHeight( moduleSize(module) );
  back = loadImage(BUS_PATH + "background.jpg");
  busBackground = createGraphics(w, h);
  busBackground.beginDraw();
  busBackground.colorMode(HSB, 360, 100, 100, 100);
  busBackground.image( pImageCut(back, CENTER, CENTER, w, h) , 0, 0);
  busBackground.fill(0, 0, 0, 40);
  busBackground.noStroke();
  busBackground.rect(0, 0, w, h);
  busBackground.endDraw();
  busBackground.mask( sizeToModuleMask( moduleSize(module) ) );
  /* ここまで追加 */

/* 略 */
```

# BusRModule用関数の準備

新しいファイル`RM_Bus.pde`を作ります。
BusRModuleの描画用関数`drawBusRModule()`、バス時刻表更新用の関数`updateBus()`を作ります。

``` java:RM_Bus.pde
void drawBusRModule(Area area) {
  
}

boolean updateBus() {
  return false;
}
```

# 時刻表スプレッドシートの作成

https://docs.google.com/spreadsheets/d/1B4EFM564k4pHl6AIi6_jf4Lo035YzOb5l5er8r44sCQ/edit?usp=sharing

上記のスプレッドシートを、ご自身のアカウントのGoogleドライブ内に複製してください。

スプレッドシートに含まれる値についての説明は以下の通りです。

| 変数名 | 意味 |
| :-- | :-- |
| line_name | バスの系統名（「67系統」など） |
| departure_hour | 出発時刻の「時」 |
| departure_minute | 出発時刻の「分」 |
| is_holiday | 土日祝ダイヤかどうか |

スプレッドシート上にあるデータをWeb API経由で取得するために、Google App Scriptを使用します。
スプレッドシート内のタブから、「拡張機能 > App Script」を選択してください。

![スクリーンショット 2021-10-31 18.09.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/368192/5eca9fe7-0d45-e8bd-848d-9f346757fdb5.png)

エディタが表示されるので、下記のプログラムを入力します。

``` js:index.gs
function getData(id, sheetName) {
  var sheet = SpreadsheetApp.openById(id).getSheetByName(sheetName);
  var rows = sheet.getDataRange().getValues();
  var keys = rows.splice(0, 1)[0];
  return rows.map(function(row) {
    var obj = {}
    row.map(function(item, index) {
      obj[keys[index]] = item;
    });
    return obj;
  });
}

function doGet(e) {
  var data = getData('URLから取り出した文字列', 'index');
  var output = ContentService.createTextOutput(JSON.stringify(data, null, 2));
  output.setMimeType(ContentService.MimeType.TEXT);
  return output;
}
```

`URLから取り出した文字列`の部分は、下記がスプレッドシートのURLのとき、

```
https://docs.google.com/spreadsheets/d/1B4EFM564k4pHl6GIi6_jf4Lo035YzOb5l5er8rn4sCQ/edit#gid=0
```

`d/`と`/edit`の間にある、`1B4EFM564k4pHl6GIi6_jf4Lo035YzOb5l5er8rn4sCQ`に置換します。
これはあくまで例ですから、実装するときには、先ほど作成したスプレッドシートのURLを参照してください。

次に、画面上部の「デプロイ」ボタンをクリックし、「新しいデプロイ」を選択します。

![スクリーンショット 2021-10-31 18.35.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/368192/b2ec879d-670b-e03b-4cc5-43a3609b9ad6.png)

設定用のウィンドウが出ます。
自分として実行する設定にした上で、アクセスできるユーザを全員にします。

![スクリーンショット 2021-10-31 18.36.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/368192/a889790e-8143-16ff-b1b0-4bdca4a8eb46.png)

デプロイをクリックすると、APIのURLがクリックできるようになります。

![スクリーンショット 2021-10-31 18.43.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/368192/ec7ba58d-3267-6760-ac59-c2bf4fd9e9d4.png)

URLを開くと、下のようなデータが返ってきます。

``` json:データ例
[
    {
        "line_name": "74系統",
        "departure_hour": 6,
        "departure_minute": 54,
        "is_holiday": false
    },
    {
        "line_name": "74系統",
        "departure_hour": 6,
        "departure_minute": 54,
        "is_holiday": true
    },
    {
        "line_name": "67系統",
        "departure_hour": 7,
        "departure_minute": 25,
        "is_holiday": false
    },
    // 省略
```

得られたURLは、プログラムの中にも入れておきます。

``` java:DigitalSignage.pde
/* 略 */

final String WEATHER_API_KEY = "取得したKey";
final String BUS_API_URL = "取得したURL"; /* 追加 */

/* 略 */
```

これでWeb APIの実装が完了しました。


# バス時刻表APIを叩く

関数`updateBus()`で、バスの時刻表を取得します。
WeatherRModuleのときと同様に、JSONArrayやJSONObjectを取得します。
取り出した値は、配列に格納します。

BusRModuleで使う変数を定義しておきます。

``` java:DigitalSignage.pde
/* 略 */

// BusRModule
PImage busMap;
PGraphics busBackground;
/* ここから追加 */
String[] lineNames;
int[] departureHours;
int[] departureMinutes;
boolean isUpdatedBus = false;
final String BUSSTOP_START = "中央小学校前";
final String BUSSTOP_END = "函館駅前";
/* ここまで追加 */

/* 略 */
```

Web API上で使われている変数名を配列で定義します。

``` java:RM_Bus.pde
/* 略 */

boolean updateBus() {
  final String[] keys = {"line_name", "departure_hour", "departure_minute", "is_holiday"}; /* 追加 */
}
```

取得したJSONを入れておくための変数と、try-catch文を作ります。
tryの中では、jsonを取得して変数へ代入します。

``` java:RM_Bus.pde
/* 略 */

boolean updateBus() {
  final String[] keys = {"line_name", "departure_hour", "departure_minute", "is_holiday"};
  
  /* ここから追加 */
  processing.data.JSONArray json = null;
  try {
    json = loadJSONArray(BUS_API_URL);
    println("updateBus(): バス時刻表を取得しました。");
  } catch (Exception e) {
    println("updateBus(): バス時刻表を取得できませんでした。" + e);
    return false;
  }
  /* ここまで追加 */

  return true;
}
```

JSONの中から、今日が平日であれば平日ダイヤ、土日祝であれば土日祝ダイヤのみに絞ります。
絞ったあとの件数分だけ、配列の大きさを確保しておきます。

``` java:RM_Bus.pde
/* 略 */

boolean updateBus() {
  final String[] keys = {"line_name", "departure_hour", "departure_minute", "is_holiday"};
  
  processing.data.JSONArray json = null;
  try {
    json = loadJSONArray(BUS_API_URL);

    /* ここから追加 */
    // 平日なら平日ダイヤ、土日祝なら土日祝ダイヤだけの数を数える
    int dataCount = 0;
    for (int row = 0; row < json.size(); row++) {
      if (json.getJSONObject(row).getBoolean(keys[3]) == isHoliday) {
        dataCount++;
      }
    }
  
    lineNames = new String[dataCount];
    departureHours = new int[dataCount];
    departureMinutes = new int[dataCount];
    /* ここまで追加 */

    println("updateBus(): バス時刻表を取得しました。");
  } catch (Exception e) {
    println("updateBus(): バス時刻表を取得できませんでした。" + e);
    return false;
  }

  return true;
}
```

JSONから値を取り出します。

``` java:RM_Bus.pde
/* 略 */

boolean updateBus() {
  final String[] keys = {"line_name", "departure_hour", "departure_minute", "is_holiday"};
  
  processing.data.JSONArray json = null;
  try {
    json = loadJSONArray(BUS_API_URL);

    // 平日なら平日ダイヤ、土日祝なら土日祝ダイヤだけの数を数える
    int dataCount = 0;
    for (int row = 0; row < json.size(); row++) {
      if (json.getJSONObject(row).getBoolean(keys[3]) == isHoliday) {
        dataCount++;
      }
    }
  
    lineNames = new String[dataCount];
    departureHours = new int[dataCount];
    departureMinutes = new int[dataCount];
    
    /* ここから追加 */
    // JSONから系統名、時刻を取り出して保存
    int i = 0;
    for (int row = 0; row < json.size(); row++) {
      processing.data.JSONObject obj = json.getJSONObject(row);
      if (obj.getBoolean(keys[3]) == isHoliday) {
        lineNames[i] = obj.getString(keys[0]);
        departureHours[i] = obj.getInt(keys[1]);
        departureMinutes[i] = obj.getInt(keys[2]);
        println("[" + lineNames[i] + "] " + nf(departureHours[i], 2) + ":" + nf(departureMinutes[i], 2));
        i++;
      }
    }
    /* ここまで追加 */

    println("updateBus(): バス時刻表を取得しました。");
  } catch (Exception e) {
    println("updateBus(): バス時刻表を取得できませんでした。" + e);
    return false;
  }

  return true;
}
```

関数`updateBus()`が実装できたので、この関数を呼び出すようにします。
天気データを更新するタイミングは、以下の2つとします。

- デジタルサイネージを起動したとき
- 1日経過したとき

関数`updateBus()`で正しく値を取得できたかを表す変数`isUpdatedBus`への代入として呼び出します。

``` java:DigitalSignage.pde
/* 略 */

void updateDatas() {
  updateDate();
  
  final boolean isUpdatedDay = (day != beforeDay);
  final boolean isUpdatedSecond = (second != beforeSecond);

  if (isUpdatedDay) {
    println("日付が変わりました。");

    if (!updateIsHoliday()) {
      println("ネットワークに接続できていない可能性があります。接続できているか確認してください。");
    }

    youbi = calcYoubi(year, month, day);
    youbiString = youbiToString(youbi);
    isUpdatedBus = updateBus(); /* 追加 */
    
    beforeDay = day;
  }
  
  if (isUpdatedSecond) {
    if (second % STAY_SECOND == 0) {
      updateNowPageID(true);
    }
    if (minute + second == 0) {
      println(hour + "時になりました。");
    }
    
    beforeSecond = second;
  }
}

/* 略 */
```

``` java:Initialize.pde
/* 略 */

void initialize() {
  initializeDate();
  initializeImage();
  initializeGrid();
  initializePlaceholder();
  initializeRModuleBackground();
  
  isUpdatedWeather = updateWeather();
  isUpdatedBus = updateBus(); /* 追加 */
}

/* 略 */
```

これでデータの更新処理が実装できました。


# BusRModuleの描画

データが変数に代入できている状態になったので、画面上に表示するところまで実装します。

置換可能な**ReplaceableModule**を実装するとき、最低限定めなければいけないことは以下です。

- 描画時の基準となるエリア（Area.area1〜Area.area8のいずれか）
- 描画時のサイズ（Size.S〜Size.Lのいずれか）

まずはBusRModuleの描画時の基準となるエリアについて説明します。
BusRModuleの描画の基準になるのは、8つある表示エリアのうち`Area.area3`です。

![0038e.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/368192/fbc1ffa1-5fa7-67eb-caee-6d9a0d1efa53.png)

![0023.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/368192/9fdb7e0b-010e-049f-f417-7e36d1fae0b2.png)

BusRModuleの実装に伴い、いくつかの変数に追加するべきものがあります。
RModuleに`Bus`を追加しておきます。

``` java:DigitalSignage.pde
/* 略 */

enum RModule {
  Weather,
  Bus /* 追加 */
}

/* 略 */
```

関数`moduleSize()`もBusRModuleに対応させます。
BusRModuleのサイズは`Size.L`です。

``` java:DigitalSignage.pde
Size moduleSize(RModule module) {
  if (module == RModule.Weather) return Size.M;
  if (module == RModule.Bus) return Size.L; /* 追加 */
  return Size.S;
}
```

次に関数`drawBusRModule()`の実装に入ります。
`Area.area3`の左上の座標、BusRModuleの幅と高さを取得します。

``` java:RM_Bus.pde
void drawBusRModule(Area area) {
  /* ここから追加 */
  RModule module = RModule.Bus;
  Size size = moduleSize(module);
  
  int x = layoutGuideX(area);
  int y = layoutGuideY(area);
  int w = moduleWidth(size);
  int h = moduleHeight(size);
  /* ここまで追加 */
}
```

BusRModuleのタイトルの描画を実装します。

``` java:RM_Bus.pde
void drawBusRModule(Area area) {
  RModule module = RModule.Bus;
  Size size = moduleSize(module);
  
  int x = layoutGuideX(area);
  int y = layoutGuideY(area);
  int w = moduleWidth(size);
  int h = moduleHeight(size);
  
  /* ここから追加 */
  String title = "函館バス（平日ダイヤ）";
  if (isHoliday) title = "函館バス（土日祝ダイヤ）";
  drawText(LEFT, BASELINE, WHITE_COLOR, 32, title, x+50, y+50);
  /* ここまで追加 */
}
```

データ更新が正しくできなかったときの処理を実装します。

``` java:RM_Bus.pde
void drawBusRModule(Area area) {
  RModule module = RModule.Bus;
  Size size = moduleSize(module);
  
  int x = layoutGuideX(area);
  int y = layoutGuideY(area);
  int w = moduleWidth(size);
  int h = moduleHeight(size);
  
  String title = "函館バス（平日ダイヤ）";
  if (isHoliday) title = "函館バス（土日祝ダイヤ）";
  drawText(LEFT, BASELINE, WHITE_COLOR, 32, title, x+50, y+50);
  
  /* ここから追加 */
  if (isUpdatedBus) {

  } else {
    fill(0, 0, 0, 50);
    noStroke();
    rect(x, y, w, h, MODULE_RECT_ROUND);
    
    drawText(CENTER, CENTER, WHITE_COLOR, 24, "BusModule\nデータを取得できません", x+w/2, y+h/2);
  }
  /* ここまで追加 */
}
```

データが正しく取得できた時の描画処理を実装します。
まずはバス停名を表示する部分を実装します。

``` java:RM_Bus.pde
void drawBusRModule(Area area) {
  RModule module = RModule.Bus;
  Size size = moduleSize(module);
  
  int x = layoutGuideX(area);
  int y = layoutGuideY(area);
  int w = moduleWidth(size);
  int h = moduleHeight(size);
  
  String title = "函館バス（平日ダイヤ）";
  if (isHoliday) title = "函館バス（土日祝ダイヤ）";
  drawText(LEFT, BASELINE, WHITE_COLOR, 32, title, x+50, y+50);
  
  if (isUpdatedBus) {
    /* ここから追加 */
    noStroke();
    fill(GREEN_COLOR);
    rect(x+50, y+120, w/2-100, 60);
    rect(x+w/2+50, y+120, w/2-100, 60);
    
    // 始点と終点のバス停名を表示し、間に矢印
    drawText(CENTER, BASELINE, WHITE_COLOR, 32, BUSSTOP_START, x+50+(w/2-100)/2, y+130);
    drawText(CENTER, BASELINE, WHITE_COLOR, 32, BUSSTOP_END, x+w/2+50+(w/2-100)/2, y+130);
    drawText(CENTER, BASELINE, WHITE_COLOR, 32, "→", x+w/2, y+130);
    /* ここまで追加 */
  } else {
    fill(0, 0, 0, 50);
    noStroke();
    rect(x, y, w, h, MODULE_RECT_ROUND);
    
    drawText(CENTER, CENTER, WHITE_COLOR, 24, "BusModule\nデータを取得できません", x+w/2, y+h/2);
  }
}
```

現在の時刻以降の2件を表示する処理を実装します。
取得した時刻表の件数分実行するfor文と、時刻の「時(departure_hour)」が現在の「時(hour)」と等しい時、現在より大きいときのif文を実装します。
`count`で、現在の時刻から近い2件を数えます。

``` java:RM_Bus.pde
void drawBusRModule(Area area) {
  RModule module = RModule.Bus;
  Size size = moduleSize(module);
  
  int x = layoutGuideX(area);
  int y = layoutGuideY(area);
  int w = moduleWidth(size);
  int h = moduleHeight(size);
  
  String title = "函館バス（平日ダイヤ）";
  if (isHoliday) title = "函館バス（土日祝ダイヤ）";
  drawText(LEFT, BASELINE, WHITE_COLOR, 32, title, x+50, y+50);
  
  if (isUpdatedBus) {
    noStroke();
    fill(GREEN_COLOR);
    rect(x+50, y+120, w/2-100, 60);
    rect(x+w/2+50, y+120, w/2-100, 60);
    
    // 始点と終点のバス停名を表示し、間に矢印
    drawText(CENTER, BASELINE, WHITE_COLOR, 32, BUSSTOP_START, x+50+(w/2-100)/2, y+130);
    drawText(CENTER, BASELINE, WHITE_COLOR, 32, BUSSTOP_END, x+w/2+50+(w/2-100)/2, y+130);
    drawText(CENTER, BASELINE, WHITE_COLOR, 32, "→", x+w/2, y+130);
    
    /* ここから追加 */
    int count = 0;
    for (int i = 0; i < lineNames.length; i++) {
      if (hour == departureHours[i]) {
        
      }
      if (hour < departureHours[i]) {
        
      }
      // 表示する2件が揃ったらこれ以降のダイヤは見ない
      if (count == 2) break;
    }
    /* ここまで追加 */
  } else {
    fill(0, 0, 0, 50);
    noStroke();
    rect(x, y, w, h, MODULE_RECT_ROUND);
    
    drawText(CENTER, CENTER, WHITE_COLOR, 24, "BusModule\nデータを取得できません", x+w/2, y+h/2);
  }
}
```

現在時刻から近い2件を取得するときには、次の2つの条件が使えます。

- 時刻表の`departure_hour`と現在時刻の`hour`が同値で、時刻表の`departure_minute`が現在時刻の`minute`以上
- 時刻表の`departure_hour`より現在時刻の`hour`が大きい

一つ目の条件とそのときの処理を、for文内のif文1つ目で実装します。

``` java:RM_Bus.pde
void drawBusRModule(Area area) {
  RModule module = RModule.Bus;
  Size size = moduleSize(module);
  
  int x = layoutGuideX(area);
  int y = layoutGuideY(area);
  int w = moduleWidth(size);
  int h = moduleHeight(size);
  
  String title = "函館バス（平日ダイヤ）";
  if (isHoliday) title = "函館バス（土日祝ダイヤ）";
  drawText(LEFT, BASELINE, WHITE_COLOR, 32, title, x+50, y+50);
  
  if (isUpdatedBus) {
    noStroke();
    fill(GREEN_COLOR);
    rect(x+50, y+120, w/2-100, 60);
    rect(x+w/2+50, y+120, w/2-100, 60);
    
    // 始点と終点のバス停名を表示し、間に矢印
    drawText(CENTER, BASELINE, WHITE_COLOR, 32, BUSSTOP_START, x+50+(w/2-100)/2, y+130);
    drawText(CENTER, BASELINE, WHITE_COLOR, 32, BUSSTOP_END, x+w/2+50+(w/2-100)/2, y+130);
    drawText(CENTER, BASELINE, WHITE_COLOR, 32, "→", x+w/2, y+130);
    
    int count = 0;
    for (int i = 0; i < lineNames.length; i++) {
      if (hour == departureHours[i]) {
        /* ここから追加 */
        // すでに出発した便は飛ばす
        if (departureMinutes[i] < minute) continue;
        // 「13:00」のように発車時刻表示
        String time = nf(departureHours[i], 2) + ":" + nf(departureMinutes[i], 2);
        drawText(CENTER, BASELINE, WHITE_COLOR, 48, time, x+50+(w/2-100)/2, y+220+70*count-16);
        // 「67系統」のように系統名表示
        drawText(CENTER, BASELINE, WHITE_COLOR, 32, lineNames[i], x+w/2+50+(w/2-100)/2, y+220+70*count);
        // 「30分後」のように発車までの時間（分）表示、「67系統」のように系統名表示
        int remainMinute = departureMinutes[i] - minute;
        drawText(CENTER, BASELINE, WHITE_COLOR, 32, remainMinute+"分後", x+w/2, y+220+70*count);
        // 文字の下に緑の線を表示
        stroke(WHITE_COLOR, 50);
        line(x+50, y+255+70*count, x+w-50, y+260+70*count);
        
        count++;
        /* ここまで追加 */
      }
      if (hour < departureHours[i]) {
        
      }
      // 表示する2件が揃ったらこれ以降のダイヤは見ない
      if (count == 2) break;
    }
  } else {
    fill(0, 0, 0, 50);
    noStroke();
    rect(x, y, w, h, MODULE_RECT_ROUND);
    
    drawText(CENTER, CENTER, WHITE_COLOR, 24, "BusModule\nデータを取得できません", x+w/2, y+h/2);
  }
}
```

2つ目の条件とそのときの処理を、for文内のif文2つ目で実装します。

``` java:RM_Bus.pde
void drawBusRModule(Area area) {
  RModule module = RModule.Bus;
  Size size = moduleSize(module);
  
  int x = layoutGuideX(area);
  int y = layoutGuideY(area);
  int w = moduleWidth(size);
  int h = moduleHeight(size);
  
  String title = "函館バス（平日ダイヤ）";
  if (isHoliday) title = "函館バス（土日祝ダイヤ）";
  drawText(LEFT, BASELINE, WHITE_COLOR, 32, title, x+50, y+50);
  
  if (isUpdatedBus) {
    noStroke();
    fill(GREEN_COLOR);
    rect(x+50, y+120, w/2-100, 60);
    rect(x+w/2+50, y+120, w/2-100, 60);
    
    // 始点と終点のバス停名を表示し、間に矢印
    drawText(CENTER, BASELINE, WHITE_COLOR, 32, BUSSTOP_START, x+50+(w/2-100)/2, y+130);
    drawText(CENTER, BASELINE, WHITE_COLOR, 32, BUSSTOP_END, x+w/2+50+(w/2-100)/2, y+130);
    drawText(CENTER, BASELINE, WHITE_COLOR, 32, "→", x+w/2, y+130);
    
    int count = 0;
    for (int i = 0; i < lineNames.length; i++) {
      if (hour == departureHours[i]) {
        // すでに出発した便は飛ばす
        if (departureMinutes[i] < minute) continue;
        // 「13:00」のように発車時刻表示
        String time = nf(departureHours[i], 2) + ":" + nf(departureMinutes[i], 2);
        drawText(CENTER, BASELINE, WHITE_COLOR, 48, time, x+50+(w/2-100)/2, y+220+70*count-16);
        // 「67系統」のように系統名表示
        drawText(CENTER, BASELINE, WHITE_COLOR, 32, lineNames[i], x+w/2+50+(w/2-100)/2, y+220+70*count);
        // 「30分後」のように発車までの時間（分）表示、「67系統」のように系統名表示
        int remainMinute = departureMinutes[i] - minute;
        drawText(CENTER, BASELINE, WHITE_COLOR, 32, remainMinute+"分後", x+w/2, y+220+70*count);
        // 文字の下に緑の線を表示
        stroke(WHITE_COLOR, 50);
        line(x+50, y+255+70*count, x+w-50, y+260+70*count);
        
        count++;
      }
      if (hour < departureHours[i]) {
        /* ここから追加 */
        String time = nf(departureHours[i], 2) + ":" + nf(departureMinutes[i], 2);
        drawText(CENTER, BASELINE, WHITE_COLOR, 48, time, x+50+(w/2-100)/2, y+220+70*count-16);
        
        drawText(CENTER, BASELINE, WHITE_COLOR, 32, lineNames[i], x+w/2+50+(w/2-100)/2, y+220+70*count);
        
        int remainMinute = (departureHours[i] - hour)*60 + (departureMinutes[i] - minute);
        drawText(CENTER, BASELINE, WHITE_COLOR, 32, remainMinute+"分後", x+w/2, y+220+70*count);
        // 文字の下に緑の線を表示
        stroke(WHITE_COLOR, 50);
        line(x+50, y+255+70*count, x+w-50, y+260+70*count);
        
        count++;
        /* ここまで追加 */
      }
      // 表示する2件が揃ったらこれ以降のダイヤは見ない
      if (count == 2) break;
    }
  } else {
    fill(0, 0, 0, 50);
    noStroke();
    rect(x, y, w, h, MODULE_RECT_ROUND);
    
    drawText(CENTER, CENTER, WHITE_COLOR, 24, "BusModule\nデータを取得できません", x+w/2, y+h/2);
  }
}
```

バス停までのマップを表示します。
マップの画像の左右と下に50pxのスペースをあけ、画像を縮小して配置します。

``` java:RM_Bus.pde
void drawBusRModule(Area area) {
  RModule module = RModule.Bus;
  Size size = moduleSize(module);
  
  int x = layoutGuideX(area);
  int y = layoutGuideY(area);
  int w = moduleWidth(size);
  int h = moduleHeight(size);
  
  String title = "函館バス（平日ダイヤ）";
  if (isHoliday) title = "函館バス（土日祝ダイヤ）";
  drawText(LEFT, BASELINE, WHITE_COLOR, 32, title, x+50, y+50);
  
  if (isUpdatedBus) {
    noStroke();
    fill(GREEN_COLOR);
    rect(x+50, y+120, w/2-100, 60);
    rect(x+w/2+50, y+120, w/2-100, 60);
    
    // 始点と終点のバス停名を表示し、間に矢印
    drawText(CENTER, BASELINE, WHITE_COLOR, 32, BUSSTOP_START, x+50+(w/2-100)/2, y+130);
    drawText(CENTER, BASELINE, WHITE_COLOR, 32, BUSSTOP_END, x+w/2+50+(w/2-100)/2, y+130);
    drawText(CENTER, BASELINE, WHITE_COLOR, 32, "→", x+w/2, y+130);
    
    int count = 0;
    for (int i = 0; i < lineNames.length; i++) {
      if (hour == departureHours[i]) {
        // すでに出発した便は飛ばす
        if (departureMinutes[i] < minute) continue;
        // 「13:00」のように発車時刻表示
        String time = nf(departureHours[i], 2) + ":" + nf(departureMinutes[i], 2);
        drawText(CENTER, BASELINE, WHITE_COLOR, 48, time, x+50+(w/2-100)/2, y+220+70*count-16);
        // 「67系統」のように系統名表示
        drawText(CENTER, BASELINE, WHITE_COLOR, 32, lineNames[i], x+w/2+50+(w/2-100)/2, y+220+70*count);
        // 「30分後」のように発車までの時間（分）表示、「67系統」のように系統名表示
        int remainMinute = departureMinutes[i] - minute;
        drawText(CENTER, BASELINE, WHITE_COLOR, 32, remainMinute+"分後", x+w/2, y+220+70*count);
        // 文字の下に緑の線を表示
        stroke(WHITE_COLOR, 50);
        line(x+50, y+255+70*count, x+w-50, y+260+70*count);
        
        count++;
      }
      if (hour < departureHours[i]) {
        String time = nf(departureHours[i], 2) + ":" + nf(departureMinutes[i], 2);
        drawText(CENTER, BASELINE, WHITE_COLOR, 48, time, x+50+(w/2-100)/2, y+220+70*count-16);
        
        drawText(CENTER, BASELINE, WHITE_COLOR, 32, lineNames[i], x+w/2+50+(w/2-100)/2, y+220+70*count);
        
        int remainMinute = (departureHours[i] - hour)*60 + (departureMinutes[i] - minute);
        drawText(CENTER, BASELINE, WHITE_COLOR, 32, remainMinute+"分後", x+w/2, y+220+70*count);
        // 文字の下に緑の線を表示
        stroke(WHITE_COLOR, 50);
        line(x+50, y+255+70*count, x+w-50, y+260+70*count);
        
        count++;
      }
      // 表示する2件が揃ったらこれ以降のダイヤは見ない
      if (count == 2) break;
    }

    image(busMap, x+50, y+h-(w-100)*9.0/16.0-50, w-100, (w-100)*9.0/16.0); /* 追加 */
  } else {
    fill(0, 0, 0, 50);
    noStroke();
    rect(x, y, w, h, MODULE_RECT_ROUND);
    
    drawText(CENTER, CENTER, WHITE_COLOR, 24, "BusModule\nデータを取得できません", x+w/2, y+h/2);
  }
}
```

最後に、BusRModuleの背景を表示します。

``` java:RM_Bus.pde
void drawBusRModule(Area area) {
  RModule module = RModule.Bus;
  Size size = moduleSize(module);
  
  int x = layoutGuideX(area);
  int y = layoutGuideY(area);
  int w = moduleWidth(size);
  int h = moduleHeight(size);

  image(busBackground, x, y, w, h); /* 追加 */
  
  String title = "函館バス（平日ダイヤ）";
  if (isHoliday) title = "函館バス（土日祝ダイヤ）";
  drawText(LEFT, BASELINE, WHITE_COLOR, 32, title, x+50, y+50);
  
  if (isUpdatedBus) {
    noStroke();
    fill(GREEN_COLOR);
    rect(x+50, y+120, w/2-100, 60);
    rect(x+w/2+50, y+120, w/2-100, 60);
    
    // 始点と終点のバス停名を表示し、間に矢印
    drawText(CENTER, BASELINE, WHITE_COLOR, 32, BUSSTOP_START, x+50+(w/2-100)/2, y+130);
    drawText(CENTER, BASELINE, WHITE_COLOR, 32, BUSSTOP_END, x+w/2+50+(w/2-100)/2, y+130);
    drawText(CENTER, BASELINE, WHITE_COLOR, 32, "→", x+w/2, y+130);
    
    int count = 0;
    for (int i = 0; i < lineNames.length; i++) {
      if (hour == departureHours[i]) {
        // すでに出発した便は飛ばす
        if (departureMinutes[i] < minute) continue;
        // 「13:00」のように発車時刻表示
        String time = nf(departureHours[i], 2) + ":" + nf(departureMinutes[i], 2);
        drawText(CENTER, BASELINE, WHITE_COLOR, 48, time, x+50+(w/2-100)/2, y+220+70*count-16);
        // 「67系統」のように系統名表示
        drawText(CENTER, BASELINE, WHITE_COLOR, 32, lineNames[i], x+w/2+50+(w/2-100)/2, y+220+70*count);
        // 「30分後」のように発車までの時間（分）表示、「67系統」のように系統名表示
        int remainMinute = departureMinutes[i] - minute;
        drawText(CENTER, BASELINE, WHITE_COLOR, 32, remainMinute+"分後", x+w/2, y+220+70*count);
        // 文字の下に緑の線を表示
        stroke(WHITE_COLOR, 50);
        line(x+50, y+255+70*count, x+w-50, y+260+70*count);
        
        count++;
      }
      if (hour < departureHours[i]) {
        String time = nf(departureHours[i], 2) + ":" + nf(departureMinutes[i], 2);
        drawText(CENTER, BASELINE, WHITE_COLOR, 48, time, x+50+(w/2-100)/2, y+220+70*count-16);
        
        drawText(CENTER, BASELINE, WHITE_COLOR, 32, lineNames[i], x+w/2+50+(w/2-100)/2, y+220+70*count);
        
        int remainMinute = (departureHours[i] - hour)*60 + (departureMinutes[i] - minute);
        drawText(CENTER, BASELINE, WHITE_COLOR, 32, remainMinute+"分後", x+w/2, y+220+70*count);
        // 文字の下に緑の線を表示
        stroke(WHITE_COLOR, 50);
        line(x+50, y+255+70*count, x+w-50, y+260+70*count);
        
        count++;
      }
      // 表示する2件が揃ったらこれ以降のダイヤは見ない
      if (count == 2) break;
    }

    image(busMap, x+50, y+h-(w-100)*9.0/16.0-50, w-100, (w-100)*9.0/16.0);
  } else {
    fill(0, 0, 0, 50);
    noStroke();
    rect(x, y, w, h, MODULE_RECT_ROUND);
    
    drawText(CENTER, CENTER, WHITE_COLOR, 24, "BusModule\nデータを取得できません", x+w/2, y+h/2);
  }
}
```

これで描画用関数が実装できたので、`drawModules()`内で呼び出すようにします。

``` java:DigitalSignage.pde
/* 略 */

void drawModules() {
  if (nowPageID == 0) {
    drawFullImageModule(background);
    drawGridModule();
    drawPlaceholderModule();
    drawWeatherRModule(Area.area1);
    drawBusRModule(Area.area3); /* 追加 */
  } else if (nowPageID == 1) {

/* 略 */
```

ここまで実装できたら実行してみてください。
最新2件の発車時刻と、発車時刻までの時間、バス停までのマップが正しく表示されているはずです。

![0023.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/368192/9fdb7e0b-010e-049f-f417-7e36d1fae0b2.png)


# 最後に

これで直近2件のバス時刻表を表示する「BusRModule」が実装できました。
次は、**[ごみ出しカレンダーを表示する「GomiRModule」](https://qiita.com/ohayota/items/40ed5db352a688637f83)**を作ってみましょう。

https://qiita.com/ohayota/items/40ed5db352a688637f83