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.

【Bラボデジタルサイネージ14】ごみ出しカレンダーを表示する「GomiRModule」

Last updated at Posted at 2021-11-01

本記事は、株式会社 函館ラボラトリが運営する「Bラボ」における、大人向け「看板アプリ(デジタルサイネージ)を作るコース」用教材テキストです。

  1. 【Bラボデジタルサイネージ1】Processing+ラズパイでデジタルサイネージを作る!
  2. 【Bラボデジタルサイネージ2】ラズパイ初期設定
  3. 【Bラボデジタルサイネージ3】準備プログラム作成&機能実現の方針決定
  4. 【Bラボデジタルサイネージ4】レイアウトの基準になるグリッドを表示する「GridModule」
  5. 【Bラボデジタルサイネージ5】レイアウトの基準になる枠を表示する「PlaceholderModule」
  6. 【Bラボデジタルサイネージ6】画像を全画面表示する「FullImageModule」
  7. 【Bラボデジタルサイネージ7】ページの自動切り替え
  8. 【Bラボデジタルサイネージ8】設置されている場所の名前を表示する「LocationModule」
  9. 【Bラボデジタルサイネージ9】現在の時間を表示する「DateModule」
  10. 【Bラボデジタルサイネージ10】ページ切り替えの時間が分かる「ProgressBarModule」
  11. 【Bラボデジタルサイネージ11】現在の表示中のページが分かる「PageControlModule」
  12. 【Bラボデジタルサイネージ12】現在の天気を表示する「WeatherRModule」
  13. 【Bラボデジタルサイネージ13】直近2件のバス時刻表を表示する「BusRModule」
  14. 【Bラボデジタルサイネージ14】ごみ出しカレンダーを表示する「GomiRModule」(本記事)
  15. 【Bラボデジタルサイネージ15】(発展編)ツイートを表示する「TwitterRModule」
  16. 【Bラボデジタルサイネージ16】開店/閉店を表示する「OpenCloseRModule」
  17. 【Bラボデジタルサイネージ17】部屋の温度を表示する「TemperatureRModule」
  18. 【Bラボデジタルサイネージ18】部屋の明るさを表示する「BrightnessRModule」
  19. 【Bラボデジタルサイネージ19】起動画面を表示する「LaunchingScreenModule」
  20. 【Bラボデジタルサイネージ20】RModuleの影を実装

前提

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

作るもの

Googleスプレッドシート上に打ち込んだごみカレンダーを使い、直近1週間のごみ収集の予定を表示します。
(ごみカレンダーは、Code for Hakodateによって、CC-BYライセンスのもとで提供されています。)

0004.png

画像の準備

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

background.jpg

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

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

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

DigitalSignage.pde
/* 略 */

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

/* 略 */

GomiRModuleの背景を保持する変数を定義します。

DigitalSignage.pde
/* 略 */

/* ここから追加 */
// GomiRModule
PGraphics gomiBackground;
/* ここまで追加 */

/* 略 */

background.jpgを用いて、モジュールの背景画像を生成します。
initializeRModuleBackground()内で実装します。

Initialize.pde
/* 略 */

void initializeRModuleBackground() {
  /* 中略 */
  busBackground.rect(0, 0, w, h);
  busBackground.endDraw();
  busBackground.mask( sizeToModuleMask( moduleSize(module) ) );

  /* ここから追加 */
  module = RModule.Gomi;
  w = moduleWidth( moduleSize(module) );
  h = moduleHeight( moduleSize(module) );
  back = loadImage(GOMI_PATH + "background.jpg");
  gomiBackground = createGraphics(w, h);
  gomiBackground.beginDraw();
  gomiBackground.colorMode(HSB, 360, 100, 100, 100);
  gomiBackground.image( pImageCut(back, CENTER, CENTER, w, h) , 0, 0);
  gomiBackground.fill(0, 0, 0, 40);
  gomiBackground.noStroke();
  gomiBackground.rect(0, 0, w, h);
  gomiBackground.endDraw();
  gomiBackground.mask( sizeToModuleMask( moduleSize(module) ) );
  /* ここまで追加 */

/* 略 */

GomiRModule用関数の準備

新しいファイルRM_Gomi.pdeを作ります。
GomiRModuleの描画用関数drawGomiRModule()、ごみカレンダー更新用の関数updateGomi()を作ります。

RM_Gomi.pde
void drawGomiRModule(Area area) {
  
}

boolean updateGomi() {
  return false;
}

ごみカレンダースプレッドシートの作成

ごみカレンダーのスプレッドシートは、Code for Hakodateが作成しています。

本テキスト内では、Code for Hakodateが作成しているごみカレンダーのスプレッドシートから、データを抜き出したものを使用します。

上記のスプレッドシートを、ご自身のアカウントのGoogleドライブ内に複製してください。
スプレッドシートに含まれる値についての説明は以下の通りです。

変数名 意味
Date(YYYY/MM/DD) 日付
dow 曜日
area:n(nは1〜17の数字) 地域ごとのごみカレンダー

Code for Hakodateの提供するデータを参照すると、「MIRAI BASE」のある「美原2丁目」は「area:2」にあたります。

「area:n」の列に存在するごみ種別は下記です。

ごみ種別 プログラム中で用いる変数名
燃やせるごみ GomiTarget.Moyaseru
燃やせないごみ GomiTarget.Moyasenai
プラスチック容器包装 GomiTarget.Plastic
缶・びん・ペットボトル GomiTarget.CanBinPet

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

スクリーンショット 2021-10-31 18.09.26.png

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

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

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

スクリーンショット 2021-10-31 18.36.55.png

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

スクリーンショット 2021-10-31 18.43.04.png

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

データ例
[
  {
    "Date(YYYY/MM/DD)": "2020/04/01",
    "dow": "水",
    "area:1": "缶・びん・ペットボトル",
    "area:2": "燃やせないごみ",
    "area:3": "",
    "area:4": "",
    "area:5": "缶・びん・ペットボトル",
    "area:6": "燃やせないごみ",
    "area:7": "燃やせないごみ",
    "area:8": "燃やせないごみ",
    "area:9": "",
    "area:10": "",
    "area:11": "",
    "area:12": "缶・びん・ペットボトル",
    "area:13": "缶・びん・ペットボトル",
    "area:14": "缶・びん・ペットボトル",
    "area:15": "缶・びん・ペットボトル",
    "area:16": "",
    "area:17": "缶・びん・ペットボトル"
  },

/*  */

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

DigitalSignage.pde
/* 略 */

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

/* 略 */

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

ごみカレンダーAPIを叩く

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

GomiRModuleで使う変数を定義しておきます。
RModuleのリストに、GomiModuleを表す「Gomi」を追加します。

DigitalSignage.pde
/* 略 */

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

/* 略 */

ごみの種別をenum(列挙型)で定義しておきます。

DigitalSignage.pde
/* 略 */

/* ここから追加 */
enum GomiTarget {
  Moyaseru, 
  Moyasenai, 
  Plastic, 
  CanBinPet, 
  None
}
/* ここまで追加 */

/* 略 */

配列などの変数を定義します。

DigitalSignage.pde
/* 略 */

/* ここから追加 */
// GomiModule
PGraphics gomiBackground;
String[] gomiDows = new String[7];
GomiTarget[] gomiTargets = new GomiTarget[7];
boolean isUpdatedGomi = false;
/* ここまで追加 */

/* 略 */

次にupdateGomi()内で、データの取得を実装します。
Web API上で使われている変数名を配列で定義します。

RM_Bus.pde
/* 略 */

boolean updateGomi() {
  final String[] keys = {"Date(YYYY/MM/DD)", "dow", "area:2"}; /* 追加 */

  return true;
}

直近1週間の曜日とごみ種別を入れる配列を初期化します。
今日の日付を文字列にする処理も実装しておきます。

RM_Bus.pde
/* 略 */

boolean updateGomi() {
  final String[] keys = {"Date(YYYY/MM/DD)", "dow", "area:2"};

  /* ここから追加 */
  gomiDows = new String[7];
  gomiTargets = new GomiTarget[7];

  String today = year + "/" + nf(month, 2) + "/" + nf(day, 2);
  println(today);
  /* ここまで追加 */

  return true;
}

データ取得処理の実装の準備として、try-catch文を準備します。

RM_Bus.pde
/* 略 */

boolean updateGomi() {
  final String[] keys = {"Date(YYYY/MM/DD)", "dow", "area:2"};

  gomiDows = new String[7];
  gomiTargets = new GomiTarget[7];

  String today = year + "/" + nf(month, 2) + "/" + nf(day, 2);
  println(today);

  /* ここから追加 */
  try {
    println("updateGomi(): ごみカレンダーを取得しました。");
  } catch (Exception e) {
    println("updateGomi(): 本日分のごみカレンダーを取得できませんでした。" + e);
    return false;
  }
  /* ここまで追加 */

  return true;
}

Web APIからJSON形式のデータを取得してみましょう。

RM_Bus.pde
/* 略 */

boolean updateGomi() {
  final String[] keys = {"Date(YYYY/MM/DD)", "dow", "area:2"};

  gomiDows = new String[7];
  gomiTargets = new GomiTarget[7];

  String today = year + "/" + nf(month, 2) + "/" + nf(day, 2);
  println(today);

  try {
    /* ここから追加 */
    processing.data.JSONArray json;
    json = loadJSONArray(GOMI_API_URL);
    /* ここまで追加 */
    println("updateGomi(): ごみカレンダーを取得しました。");
  } catch (Exception e) {
    println("updateGomi(): 本日分のごみカレンダーを取得できませんでした。" + e);
    return false;
  }

  return true;
}

今日の日付が含まれているデータが、取得したデータの何個目にあるかをtodayRowNumに保持しておきます。
もし、今日の日付のデータがない場合は、取得できなかった扱いにします。

RM_Bus.pde
/* 略 */

boolean updateGomi() {
  final String[] keys = {"Date(YYYY/MM/DD)", "dow", "area:2"};

  gomiDows = new String[7];
  gomiTargets = new GomiTarget[7];

  String today = year + "/" + nf(month, 2) + "/" + nf(day, 2);
  println(today);

  try {
    processing.data.JSONArray json;
    json = loadJSONArray(GOMI_API_URL);
    
    /* ここから追加 */
    int todayRowNum = Integer.MAX_VALUE;
    for (int row = 0; row < json.size(); row++) {
      processing.data.JSONObject obj = json.getJSONObject(row);
      if (obj.getString(keys[0]).equals(today)) {
        todayRowNum = row;
        break;
      }
    }
    if (todayRowNum == Integer.MAX_VALUE) throw new Exception();
    /* ここまで追加 */
    
    println("updateGomi(): ごみカレンダーを取得しました。");
  } catch (Exception e) {
    println("updateGomi(): 本日分のごみカレンダーを取得できませんでした。" + e);
    return false;
  }

  return true;
}

今日の日付のデータが何個目にあるか特定できたら、そこから7日分のデータを取得します。
"燃やせるごみ"などのごみ回収対象がデータとして入っていた場合は、対応するGomiTarget.Moyaseruなどの変数を、配列に入れておきます。

RM_Bus.pde
/* 略 */

boolean updateGomi() {
  final String[] keys = {"Date(YYYY/MM/DD)", "dow", "area:2"};

  gomiDows = new String[7];
  gomiTargets = new GomiTarget[7];

  String today = year + "/" + nf(month, 2) + "/" + nf(day, 2);
  println(today);

  try {
    processing.data.JSONArray json;
    json = loadJSONArray(GOMI_API_URL);
    
    int todayRowNum = Integer.MAX_VALUE;
    for (int row = 0; row < json.size(); row++) {
      processing.data.JSONObject obj = json.getJSONObject(row);
      if (obj.getString(keys[0]).equals(today)) {
        todayRowNum = row;
        break;
      }
    }
    if (todayRowNum == Integer.MAX_VALUE) throw new Exception();
    
    /* ここから追加 */
    for (int i = 0; i < 7; i++) {
      int row = todayRowNum + i;
      processing.data.JSONObject obj = json.getJSONObject(row);
      String gomiTargetString = obj.getString(keys[2]);
      switch (gomiTargetString) {
      case "燃やせるごみ":
        gomiTargets[i] = GomiTarget.Moyaseru;
        break;
      case "燃やせないごみ":
        gomiTargets[i] = GomiTarget.Moyasenai;
        break;
      case "プラスチック容器包装":
        gomiTargets[i] = GomiTarget.Plastic;
        break;
      case "缶・びん・ペットボトル":
        gomiTargets[i] = GomiTarget.CanBinPet;
        break;
      default:
        gomiTargets[i] = GomiTarget.None;
      }
      String gomiDowString = obj.getString(keys[1]);
      gomiDows[i] = gomiDowString;
      println("[" + gomiDowString + "] " + gomiTargetString);
    }
    /* ここまで追加 */

    println("updateGomi(): ごみカレンダーを取得しました。");
  } catch (Exception e) {
    println("updateGomi(): 本日分のごみカレンダーを取得できませんでした。" + e);
    return false;
  }

  return true;
}

関数updateGomi()が実装できたので、この関数を呼び出すようにします。
ごみカレンダーを更新するタイミングは、以下の2つとします。

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

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

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();
    isUpdatedGomi = updateGomi(); /* 追加 */
    
    beforeDay = day;
  }
  
  if (isUpdatedSecond) {
    if (second % STAY_SECOND == 0) {
      updateNowPageID(true);
    }
    if (minute + second == 0) {
      println(hour + "時になりました。");
    }
    
    beforeSecond = second;
  }
}

/* 略 */
Initialize.pde
/* 略 */

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

/* 略 */

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

GomiRModuleの描画

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

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

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

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

0038e.png

0004.png

関数moduleSize()をGomiRModuleに対応させます。
GomiRModuleのサイズはSize.Mです。

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

ごみの種類に応じて表示するときの色を使い分けます。
表示する色を返す関数を作ります。

RM_Gomi.pde
/* 略 */

color gomiTargetToColor(GomiTarget target) {
  switch (target) {
  case Moyaseru:
    return color(340, 30, 100);
  case Moyasenai:
    return color(130, 30, 80);
  case Plastic:
    return color(30, 30, 100);
  case CanBinPet:
    return color(210, 30, 100);
  default:
    return color(0, 0, 90);
  }
}

ごみの種類の表示名を文字列として返すために、関数を定義します。

RM_Gomi.pde
/* 略 */

String gomiTargetToString(GomiTarget target) {
  switch (target) {
  case Moyaseru:
    return "燃やせるごみ";
  case Moyasenai:
    return "燃やせないごみ";
  case Plastic:
    return "プラスチック容器包装";
  case CanBinPet:
    return "缶・びん・ペットボトル";
  default:
    return "";
  }
}

次に関数drawGomiRModule()を実装します。
Area.area5の左上の座標、GomiRModuleの幅と高さを取得します。

RM_Gomi.pde
void drawGomiRModule(Area area) {
  /* ここから追加 */
  RModule module = RModule.Gomi;
  Size size = moduleSize(module);
  
  int x = layoutGuideX(area);
  int y = layoutGuideY(area);
  int w = moduleWidth(size);
  int h = moduleHeight(size);
  /* ここまで追加 */
}

/* 略 */

GomiRModuleのタイトルと背景画像をを表示します。

RM_Gomi.pde
void drawGomiRModule(Area area) {
  RModule module = RModule.Gomi;
  Size size = moduleSize(module);
  
  int x = layoutGuideX(area);
  int y = layoutGuideY(area);
  int w = moduleWidth(size);
  int h = moduleHeight(size);

  /* ここから追加 */
  image(gomiBackground, x, y, w, h);

  drawText(LEFT, BASELINE, WHITE_COLOR, 32, "美原2丁目のごみカレンダー", x+50, y+50);
  drawText(LEFT, BASELINE, WHITE_COLOR, 16, "ごみカレンダー提供元: Code for Hakodate", x+50, y+100);
  /* ここまで追加 */
}

/* 略 */

updateGomi()で取得した、1週間分のデータを表示します。
今日のごみ回収対象は大きく表示し、明日以降の6日分は小さく表示します。

RM_Gomi.pde
void drawGomiRModule(Area area) {
  RModule module = RModule.Gomi;
  Size size = moduleSize(module);
  
  int x = layoutGuideX(area);
  int y = layoutGuideY(area);
  int w = moduleWidth(size);
  int h = moduleHeight(size);

  image(gomiBackground, x, y, w, h);

  drawText(LEFT, BASELINE, WHITE_COLOR, 32, "美原2丁目のごみカレンダー", x+50, y+50);
  drawText(LEFT, BASELINE, WHITE_COLOR, 16, "ごみカレンダー提供元: Code for Hakodate", x+50, y+100);

  /* ここから追加 */
  if (isUpdatedGomi) {
    textAlign(LEFT, TOP);
    // 今日の回収ごみは大きく表示する
    for (int i = 0; i <= 0; i++) {
      GomiTarget target = gomiTargets[i];
      fill(gomiTargetToColor(target));
      rect(x+50, y+150+(h-150)*i/7.0, w-100, 70);
      drawText(LEFT, BASELINE, BLACK_COLOR, 40, gomiDows[i]+"    "+gomiTargetToString(target), x+50+10, y+160+(h-150)*i/7);
    }
    // 1日後〜3日後までの回収ごみは小さく3行で左側に表示する
    for (int i = 1; i <= 3; i++) {
      GomiTarget target = gomiTargets[i];
      fill(gomiTargetToColor(target));
      rect(x+50, y+240+(h-150)*(i-1)/7.0, (w-100)/2-20, 32);
      drawText(LEFT, BASELINE, BLACK_COLOR, 24, gomiDows[i]+"    "+gomiTargetToString(target), x+50+10, y+242+(h-150)*(i-1)/7);
    }
    // 4日後〜6日後までの回収ごみは小さく3行で右側に表示する
    for (int i = 4; i <= 6; i++) {
      GomiTarget target = gomiTargets[i];
      fill(gomiTargetToColor(target));
      rect(x+50+(w-100)/2+20, y+240+(h-150)*(i-4)/7.0, (w-100)/2-20, 32);
      drawText(LEFT, BASELINE, BLACK_COLOR, 24, gomiDows[i]+"    "+gomiTargetToString(target), x+50+10+(w-100)/2+20, y+242+(h-150)*(i-4)/7);
    }
  } else {
    fill(0, 0, 0, 50);
    noStroke();
    rect(x, y, w, h, MODULE_RECT_ROUND);
    
    drawText(CENTER, BASELINE, WHITE_COLOR, 24, "GomiModule\nデータを取得できません", x+w/2, y+h/2);
  }
  /* ここまで追加 */
}

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

DigitalSignage.pde
/* 略 */

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

/* 略 */

GomiRModuleを呼び出せるようになりました。
実行してみると、1週間分のごみカレンダーを表示できるようになっています。

0004.png

最後に

これでごみ出しカレンダーを表示する「GomiRModule」が実装できました。
**(発展編)ツイートを表示する「TwitterRModule」**を作ってみましょう。

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?