本記事は、株式会社 函館ラボラトリが運営する「Bラボ」における、大人向け「看板アプリ(デジタルサイネージ)を作るコース」用教材テキストです。
- 【Bラボデジタルサイネージ1】Processing+ラズパイでデジタルサイネージを作る!
- 【Bラボデジタルサイネージ2】ラズパイ初期設定
- 【Bラボデジタルサイネージ3】準備プログラム作成&機能実現の方針決定
- 【Bラボデジタルサイネージ4】レイアウトの基準になるグリッドを表示する「GridModule」
- 【Bラボデジタルサイネージ5】レイアウトの基準になる枠を表示する「PlaceholderModule」
- 【Bラボデジタルサイネージ6】画像を全画面表示する「FullImageModule」
- 【Bラボデジタルサイネージ7】ページの自動切り替え
- 【Bラボデジタルサイネージ8】設置されている場所の名前を表示する「LocationModule」
- 【Bラボデジタルサイネージ9】現在の時間を表示する「DateModule」
- 【Bラボデジタルサイネージ10】ページ切り替えの時間が分かる「ProgressBarModule」
- 【Bラボデジタルサイネージ11】現在の表示中のページが分かる「PageControlModule」
- 【Bラボデジタルサイネージ12】現在の天気を表示する「WeatherRModule」
- 【Bラボデジタルサイネージ13】直近2件のバス時刻表を表示する「BusRModule」(本記事)
- 【Bラボデジタルサイネージ14】ごみ出しカレンダーを表示する「GomiRModule」
- 【Bラボデジタルサイネージ15】(発展編)ツイートを表示する「TwitterRModule」
- 【Bラボデジタルサイネージ16】開店/閉店を表示する「OpenCloseRModule」
- 【Bラボデジタルサイネージ17】部屋の温度を表示する「TemperatureRModule」
- 【Bラボデジタルサイネージ18】部屋の明るさを表示する「BrightnessRModule」
- 【Bラボデジタルサイネージ19】起動画面を表示する「LaunchingScreenModule」
- 【Bラボデジタルサイネージ20】RModuleの影を実装
前提
BusRModuleでは、バス時刻表のためのデータにGoogleスプレッドシートを使用します。
あらかじめGoogleアカウントを取得しておいてください。
作るもの
Googleスプレッドシート上に打ち込んだバス時刻表を使い、直近2件のバス発車時刻を表示します。
画像の準備
BusRModuleの背景画像には、下の写真を使用します。
画像を保存し、「background.jpg」という名前をつけます。
(.png形式など別の形式の場合は、.jpg形式に変換してください。)
バス停までのマップには、下の画像を使用します。
画像を保存し、「bus_map.jpg」という名前をつけます。
(.png形式など別の形式の場合は、.jpg形式に変換してください。)
Processingの画面上のタブで、「スケッチ > スケッチフォルダーを開く」を選ぶと、開いている.pdeファイルが一覧で表示されます。
「data」フォルダの中に、新たに「bus」フォルダを作ってください。
先ほど名前をつけて保存した「background.jpg」を「data/bus」フォルダの直下においてください。(ドラッグ&ドロップ)
ここで新しく作った「data/bus」フォルダの位置を示すパスを、定数として定義します。
/* 略 */
final String AD_PATH = "ad/";
final String WEATHER_PATH = "weather/";
final String BUS_PATH = "bus/"; /* 追加 */
/* 略 */
bus/bus_map.jpg
をプログラムで読み込みます。
BusRModuleの背景を保持する変数も定義します。
/* 略 */
/* ここから追加 */
// BusRModule
PImage busMap;
PGraphics busBackground;
/* ここまで追加 */
/* 略 */
initializeImage()
の中でbus_map.jpg
を読み込みます。
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()
内で実装します。
/* 略 */
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()
を作ります。
void drawBusRModule(Area area) {
}
boolean updateBus() {
return false;
}
時刻表スプレッドシートの作成
上記のスプレッドシートを、ご自身のアカウントのGoogleドライブ内に複製してください。
スプレッドシートに含まれる値についての説明は以下の通りです。
変数名 | 意味 |
---|---|
line_name | バスの系統名(「67系統」など) |
departure_hour | 出発時刻の「時」 |
departure_minute | 出発時刻の「分」 |
is_holiday | 土日祝ダイヤかどうか |
スプレッドシート上にあるデータをWeb API経由で取得するために、Google App Scriptを使用します。
スプレッドシート内のタブから、「拡張機能 > App Script」を選択してください。
エディタが表示されるので、下記のプログラムを入力します。
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を参照してください。
次に、画面上部の「デプロイ」ボタンをクリックし、「新しいデプロイ」を選択します。
設定用のウィンドウが出ます。
自分として実行する設定にした上で、アクセスできるユーザを全員にします。
デプロイをクリックすると、APIのURLがクリックできるようになります。
URLを開くと、下のようなデータが返ってきます。
[
{
"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は、プログラムの中にも入れておきます。
/* 略 */
final String WEATHER_API_KEY = "取得したKey";
final String BUS_API_URL = "取得したURL"; /* 追加 */
/* 略 */
これでWeb APIの実装が完了しました。
バス時刻表APIを叩く
関数updateBus()
で、バスの時刻表を取得します。
WeatherRModuleのときと同様に、JSONArrayやJSONObjectを取得します。
取り出した値は、配列に格納します。
BusRModuleで使う変数を定義しておきます。
/* 略 */
// BusRModule
PImage busMap;
PGraphics busBackground;
/* ここから追加 */
String[] lineNames;
int[] departureHours;
int[] departureMinutes;
boolean isUpdatedBus = false;
final String BUSSTOP_START = "中央小学校前";
final String BUSSTOP_END = "函館駅前";
/* ここまで追加 */
/* 略 */
Web API上で使われている変数名を配列で定義します。
/* 略 */
boolean updateBus() {
final String[] keys = {"line_name", "departure_hour", "departure_minute", "is_holiday"}; /* 追加 */
}
取得したJSONを入れておくための変数と、try-catch文を作ります。
tryの中では、jsonを取得して変数へ代入します。
/* 略 */
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の中から、今日が平日であれば平日ダイヤ、土日祝であれば土日祝ダイヤのみに絞ります。
絞ったあとの件数分だけ、配列の大きさを確保しておきます。
/* 略 */
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から値を取り出します。
/* 略 */
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
への代入として呼び出します。
/* 略 */
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;
}
}
/* 略 */
/* 略 */
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
です。
BusRModuleの実装に伴い、いくつかの変数に追加するべきものがあります。
RModuleにBus
を追加しておきます。
/* 略 */
enum RModule {
Weather,
Bus /* 追加 */
}
/* 略 */
関数moduleSize()
もBusRModuleに対応させます。
BusRModuleのサイズはSize.L
です。
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の幅と高さを取得します。
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のタイトルの描画を実装します。
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);
/* ここまで追加 */
}
データ更新が正しくできなかったときの処理を実装します。
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);
}
/* ここまで追加 */
}
データが正しく取得できた時の描画処理を実装します。
まずはバス停名を表示する部分を実装します。
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件を数えます。
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つ目で実装します。
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つ目で実装します。
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のスペースをあけ、画像を縮小して配置します。
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の背景を表示します。
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()
内で呼び出すようにします。
/* 略 */
void drawModules() {
if (nowPageID == 0) {
drawFullImageModule(background);
drawGridModule();
drawPlaceholderModule();
drawWeatherRModule(Area.area1);
drawBusRModule(Area.area3); /* 追加 */
} else if (nowPageID == 1) {
/* 略 */
ここまで実装できたら実行してみてください。
最新2件の発車時刻と、発車時刻までの時間、バス停までのマップが正しく表示されているはずです。
最後に
これで直近2件のバス時刻表を表示する「BusRModule」が実装できました。
次は、**ごみ出しカレンダーを表示する「GomiRModule」**を作ってみましょう。