1
1

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ラボデジタルサイネージ12】現在の天気を表示する「WeatherRModule」

Last updated at Posted at 2021-10-31

本記事は、株式会社 函館ラボラトリが運営する「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の影を実装

前提

WeatherRModuleでは、天気情報を取得するために、OpenWeatherのAPIを使用します。
OpenWeatherのAPIを使用するためには、あらかじめOpenWeatherのアカウントを取得しておく必要があります。

作るもの

現在の天気を、OpenWeatherのAPIから取得して表示します。

天気データを取得できたとき
0145.png

天気データを取得できなかったとき
0003.png

背景画像の準備

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

background.jpg

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

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

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

DigitalSignage.pde
/* 略 */

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

/* 略 */

WeatherRModule用関数の準備

新しいファイルRM_Weather.pdeを作ります。
WeatherRModuleの描画用関数drawWeatherRModule()、天気データ更新用の関数updateWeather()、APIアクセス用のURLを返す関数openWeatherURL()を作ります。

RM_Weather.pde
void drawWeatherRModule(Area area) {
}

boolean updateWeather() {
  return false;
}

String openWeatherURL(float latitude, float longitude, String apiKey) {
  return "";
}

必要な変数の定義

WeatherRModuleで必要な変数について説明します。

OpenWeatherのAPIへアクセスするには、URLにいくつか値を埋め込む必要があります。
必要なものは下記です。

変数名 意味 使用場面
LATITUDE 緯度 天気を取得する地点の緯度を指定
LONGITUDE 経度 天気を取得する地点の経度を指定
WEATHER_API_KEY OpenWeatherのAPIキー データ取得時の認証のためURL内に埋め込み

実際にURLの例を挙げると、下記のようになります(なお、ダミー値が入っているので、このURLのままではデータは取得できません)。

"https://api.openweathermap.org/data/2.5/onecall?lat=41.81469&lon=140.75722&units=metric&lang=ja&appid=XXXXXXXX"

URLに使う値を、プログラム中に定義してみましょう。
緯度経度については、MIRAI BASEの建物のものを例とします。

DigitalSignage.pde
/* 略 */
boolean isHoliday;

// APIを叩くときに使用する定数
final String WEATHER_API_KEY = ""; /* 追加 */

// WeatherRModule
final float LATITUDE = 41.81469; /* 追加 */
final float LONGITUDE = 140.75722; /* 追加 */

/* 略 */

天気を取得したい地点の緯度経度を知りたい場合は、Googleマップを使います。

Googleマップで地点を検索し、地点にカーソルを当てて右クリックします。
一番上の行に緯度と経度が表示されるので、これらの値でLATITUDELONGITUDEを初期化してください。

スクリーンショット 2021-10-30 23.48.48.png

WEATHER_API_KEYの値は、OpenWeatherのWebページから取得します。
取得してあるアカウントでログインしてください。

ログイン後、右上にアカウント名が表示されている部分をクリックし、「My API keys」を選択します。

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

遷移先のWebページで、「API Keys」タブをクリックすると、API keyが表示されている欄があります。
(下図では、API keyが表示されている部分は黒塗りしています。各ユーザごとに違う値が表示されています。)

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

API keyをコピーし、この値でWEATHER_API_KEYを初期化してください。

DigitalSignage.pde
final String WEATHER_API_KEY = "コピーしたAPI keyをここに入力";

次に、APIから取得した値を保存しておくための変数を定義します。
WeatherRModuleのスクリーンショットで、必要な値を説明します。

WeatherRModule

必要な値は以下の4つです。

変数名 意味 使用場面
weatherIcon 天気アイコン 天気名に対応するアイコン
temperature 気温 現在の気温(摂氏)
humidity 湿度 現在の湿度
weatherString 天気名 現在の天気を説明する文章
DigitalSignage.pde
/* 略 */

// WeatherRModule
final float LATITUDE = 41.81469;
final float LONGITUDE = 140.75722;
PImage weatherIcon; /* 追加 */
float temperature = 0.0; /* 追加 */
int humidity = 0; /* 追加 */
String weatherString = ""; /* 追加 */

/* 略 */

変数を定義できたので、次は実際のデータを取得して入れてみます。

天気APIを叩く

まずは、天気APIを叩いてみましょう。
"https://api.openweathermap.org/data/2.5/onecall?lat=41.81469&lon=140.75722&units=metric&lang=ja&appid=XXXXXXXX" というURLの「XXXXXXXX」の部分を、自身で取得したAPI keyに置換してください。
置換できたら、ブラウザのURL欄に貼り付けてみてください。
正しく値が入力できていると、下図のような画面に遷移します。

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

見づらいですが、たくさんデータが返ってきています。
このときのURLを返す関数openWeatherURL()を実装します。

RM_Weather.pde
/* 略 */

String openWeatherURL(float latitude, float longitude, String apiKey) {
  /* return文を変更 */
  return "https://api.openweathermap.org/data/2.5/onecall?" +
         "lat=" + latitude + "&lon=" + longitude +
         "&units=metric&lang=ja&appid=" + apiKey;
}

先ほど返ってきたデータをインデントや改行で整形してみたものが、下記のようになります。

データ例
{
    "lat":41.8147,
    "lon":140.7572,
    "timezone":"Asia/Tokyo",
    "timezone_offset":32400,
    "current":{
        "dt":1635609394,
        "sunrise":1635628052,
        "sunset":1635665610,
        "temp":9.26,
        "feels_like":9.26,
        "pressure":1026,
        "humidity":75,
        "dew_point":5.06,
        "uvi":0,
        "clouds":100,
        "visibility":10000,
        "wind_speed":1.3,
        "wind_deg":312,
        "wind_gust":1.85,
        "weather":[
            {"id":804,"main":"Clouds","description":"厚い雲","icon":"04n"}
        ]},
        "minutely":[
            {"dt":1635609420,"precipitation":0},
            /* 中略 */
            {"dt":1635613020,"precipitation":0}
        ],
        "hourly":[
            {
                "dt":1635606000,
                "temp":9.22,
                "feels_like":8.73,
                "pressure":1026,
                "humidity":74,
                "dew_point":4.83,
                "uvi":0,
                "clouds":100,
                "visibility":10000,
                "wind_speed":1.53,
                "wind_deg":300,
                "wind_gust":2.07,
                "weather":[
                    {"id":804,"main":"Clouds","description":"厚い雲","icon":"04n"}
                ],
                "pop":0
            },
            /*  */

名前のついた値が、JSONという形式で返ってきています(Web APIでのデータのやりとりでよく使われるデータ構造です)。
得られたデータのうち、現在の天気を表すのは、"current"という名前がついた括弧の中にある部分です。

current内のどの変数を参照すれば良いかを表にしてみます。

変数名 意味 使用場面 JSON内の変数
weatherIcon 天気アイコン 天気名に対応するアイコン "weather"内の"icon"
temperature 気温 現在の気温(摂氏) "temp"
humidity 湿度 現在の湿度 "humidity"
weatherString 天気名 現在の天気を説明する文章 "weather"内の"description"

JSON形式のデータからこれらの値を取り出し、値を変数に代入する処理を、関数updateWeather()で実装しましょう。

JSON形式のデータから値を得るには、すでに用意されている関数を使います。
URLを指定し、JSON形式のデータを得るには、下記の処理を用います。
(書かれているURLはダミーのURLです。)

String url = "https://example.blab.com"
processing.data.JSONObject json = loadJSONObject(url);

loadJSONObject()で、括弧の中のURLから、JSON形式のデータを取得します。

取得したデータから、変数名を指定してJSONObjectを取得します。
変数名が"current"のJSONObjectを取得するときは、下記の処理を用います。

processing.data.JSONObject current = json.getJSONObject("current");

複数のJSONObjectが入っている「JSONArray」を取得するときには、下記の処理を用います。

processing.data.JSONArray weather = current.getJSONArray("weather");

JSONObjectから型を指定して値を取り出すときは、下記の処理を用います。

processing.data.JSONObject example = json.getJSONObject("example");
int exInt = example.getInt("name");
float exFloat = example.getFloat("name");
String exString = example.getString("name");

getInt()など、取り出したい値の型に応じて使い分けます。
APIから返ってきたデータのうち、湿度humidityはint型、気温temperatureはfloat型、天気の説明descriptionはString型です。

JSONArrayから0番目のJSONObjectを取り出すときは、下記の処理を使います。

processing.data.JSONObject example = json.getJSONArray("example").getJSONObject(0);

これらの処理を使い、変数への代入まで実装します。

RM_Weather.pde
/* 略 */

boolean updateWeather() {
  /* ここから追加 */
  // 現在の天気を取得する
  final String url = openWeatherURL(LATITUDE, LONGITUDE, WEATHER_API_KEY);
  final processing.data.JSONObject json = loadJSONObject(url);
  final processing.data.JSONObject current = json.getJSONObject("current");
  final processing.data.JSONArray weather = current.getJSONArray("weather");
    
  // 天気の説明、温度、湿度を取得
  weatherString = weather.getJSONObject(0).getString("description");
  temperature = current.getFloat("temp");
  humidity = current.getInt("humidity");
  /* ここまで追加 */
  return false;
}

次に、天気に対応するアイコンを取得します。
JSONのデータのうち、"icon"の値が"04n"だとすると、アイコン画像のURLは次のようになります。
http://openweathermap.org/img/wn/04n@2x.png
URLからアイコンを読みこんで、変数に代入する処理を書きます。

RM_Weather.pde
/* 略 */

boolean updateWeather() {
  // 現在の天気を取得する
  final String url = openWeatherURL(LATITUDE, LONGITUDE, WEATHER_API_KEY);
  final processing.data.JSONObject json = loadJSONObject(url);
  final processing.data.JSONObject current = json.getJSONObject("current");
  final processing.data.JSONArray weather = current.getJSONArray("weather");
    
  // 天気の説明、温度、湿度を取得
  weatherString = weather.getJSONObject(0).getString("description");
  temperature = current.getFloat("temp");
  humidity = current.getInt("humidity");

  /* ここから追加 */
  // 現在の天気に対応する画像を持ってくる
  final String iconCode = weather.getJSONObject(0).getString("icon");
  weatherIcon = loadImage("http://openweathermap.org/img/wn/" + iconCode + "@2x.png");
  /* ここまで追加 */

  return false;
}

APIから値が取得できなかったときの処理も実装します。
try-catch文で実装すると下記のようになります。

RM_Weather.pde
/* 略 */

boolean updateWeather() {
  try {
    // 現在の天気を取得する
    final String url = openWeatherURL(LATITUDE, LONGITUDE, WEATHER_API_KEY);
    final processing.data.JSONObject json = loadJSONObject(url);
    final processing.data.JSONObject current = json.getJSONObject("current");
    final processing.data.JSONArray weather = current.getJSONArray("weather");
    
    // 天気の説明、温度、湿度を取得
    weatherString = weather.getJSONObject(0).getString("description");
    temperature = current.getFloat("temp");
    humidity = current.getInt("humidity");
    
    // 現在の天気に対応する画像を持ってくる
    final String iconCode = weather.getJSONObject(0).getString("icon");
    weatherIcon = loadImage("http://openweathermap.org/img/wn/" + iconCode + "@2x.png");

    println("updateWeather(): 天気情報を取得しました。");
    return true;
  } catch (Exception e) {
    println("updateWeather(): 天気情報を取得できませんでした。" + e);
    return false;
  }
}

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

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

「デジタルサイネージを起動したとき」の処理は、関数initialize()内に記述すれば実現できます。

Initialize.pde
void initialize() {
  initializeDate();
  initializeImage();
  initializeGrid();
  initializePlaceholder();
  
  updateWeather(); /* 追加 */
}

/* 略 */

「1時間経過したとき」の処理は、関数updateDatas内に記述すれば実現できます。
記述箇所を間違えないでください。間違えると、APIへのアクセス回数が必要以上に増えてしまい、負荷を増やすことに繋がります。

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);

    beforeDay = day;
  }
  
  if (isUpdatedSecond) {
    if (second % STAY_SECOND == 0) {
      updateNowPageID(true);
    }
    if (minute + second == 0) {
      println(hour + "時になりました。");
      updateWeather(); /* 追加 */
    }
    
    beforeSecond = second;
  }
}

これで、天気データの取得処理が実装できました。

WeatherRModuleを描画

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

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

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

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

0038e.png

0145.png

Area.area1の左上の座標を取得します。

DigitalSignage.pde
void drawWeatherRModule(Area area) {
  int x = layoutGuideX(area); /* 追加 */
  int y = layoutGuideY(area); /* 追加 */
}

/* 略 */

次に、描画時のサイズについて説明します。

PlaceholderModuleのときに説明したように、角丸四角2個分のサイズなら、Size.MがWeatherRModuleのサイズになります。
このSize.Mという値から、描画エリアの幅と高さを取得する必要があります。

まずは、このようなReplaceableModule(RM)のサイズを返す関数を定義します。
関数を定義する前の準備として、RMに分類されるモジュールを列挙する列挙型変数を定義します。

DigitalSignage.pde
/* 略 */
  area7,
  area8
}

/* ここから追加 */
enum RModule {
  Weather
}
/* ここまで追加 */

/* 略 */

次に関数を作ります。

DigitalSignage.pde
/* 略 */

/* ここから追加 */
Size moduleSize(RModule module) {
  if (module == RModule.Weather) return Size.M;
  return Size.S;
}
/* ここまで追加 */

定義したmoduleSize()を使い、モジュールの幅と高さを取得します。
関数moduleWidth()と関数moduleHeight()を使います。

RM_Weather.pde
void drawWeatherRModule(Area area) {
  RModule module = RModule.Weather; /* 追加 */
  Size size = moduleSize(module); /* 追加 */
  
  int x = layoutGuideX(area);
  int y = layoutGuideY(area);
  int w = moduleWidth(size); /* 追加 */
  int h = moduleHeight(size); /* 追加 */
}

/* 略 */

ここまでで、描画の基準となる座標と、幅と高さが取得できました。
次に、文字や天気アイコンを表示します。

WeatherRModule

「現在の天気」「気象データ提供元: OpenWeather(TM)」、気温や湿度、天気と天気アイコンが必要です。

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

  /* ここから追加 */
  drawText(LEFT, BASELINE, BLACK_COLOR, 32, "現在の天気", x+50, y+50);
  drawText(LEFT, BASELINE, BLACK_COLOR, 16, "気象データ提供元: OpenWeather(TM)", x+50, y+100);
  drawText(LEFT, BASELINE, BLACK_COLOR, 64, int(temperature)+"℃ / "+humidity+"%", x+50, y+160);
  drawText(LEFT, BASELINE, BLACK_COLOR, 42, weatherString, x+50, y+260);
  image(weatherIcon, x+w-h, y+50, h, h);
  /* ここまで追加 */
}

ここまで実装したら、drawModules()内でdrawWeatherRModule()を呼び出すように変更します。

DigitalSignage.pde
void drawModules() {
  if (nowPageID == 0) {
    drawFullImageModule(background);
    drawGridModule();
    drawPlaceholderModule();
    drawWeatherRModule(Area.area1); /* 追加 */
  } else if (nowPageID == 1) {
    drawFullImageModule(adImage[0]);
  }
  drawDateModule();
  drawLocationModule();
  drawProgressBarModule();
  drawPageControlModule();
}

実行すると、下図のようになります。

0016.png

データが正しく取得できなかったときの表示も実装します。
「データが正しく取得できたか」を示す変数を定義します。

DigitalSignage.pde
/* 略 */

// WeatherRModule
final float LATITUDE = 41.81469;
final float LONGITUDE = 140.75722;
boolean isUpdatedWeather = false; /* 追加 */
PImage weatherIcon;
float temperature = 0.0;
int humidity = 0;
String weatherString = "";

/* 略 */

isUpdatedWeatherの値は、関数updateWeather()が返した値を代入したものになります。
updateWeather()を呼び出している箇所を変更します。

Initialize.pde
void initialize() {
  initializeDate();
  initializeImage();
  initializeGrid();
  initializePlaceholder();
  
  isUpdatedWeather = updateWeather(); /* 変更 */
}
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);

    beforeDay = day;
  }
  
  if (isUpdatedSecond) {
    if (second % STAY_SECOND == 0) {
      updateNowPageID(true);
    }
    if (minute + second == 0) {
      println(hour + "時になりました。");
      isUpdatedWeather = updateWeather(); /* 変更 */
    }
    
    beforeSecond = second;
  }
}

isUpdatedWeatherの値を更新できるようにしたので、isUpdatedWeatherがfalseのとき、データが取得できていないことを表示するように実装します。

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

  drawText(LEFT, BASELINE, BLACK_COLOR, 32, "現在の天気", x+50, y+50);
  drawText(LEFT, BASELINE, BLACK_COLOR, 16, "気象データ提供元: OpenWeather(TM)", x+50, y+100);

  /* ここから変更 */
  if (isUpdatedWeather) {
    drawText(LEFT, BASELINE, BLACK_COLOR, 64, int(temperature)+"℃ / "+humidity+"%", x+50, y+160);
    drawText(LEFT, BASELINE, BLACK_COLOR, 42, weatherString, x+50, y+260);
    image(weatherIcon, x+w-h, y+50, h, h);
  } else {
    fill(0, 0, 0, 50);
    noStroke();
    rect(x, y, w, h, MODULE_RECT_ROUND);
    
    drawText(CENTER, CENTER, WHITE_COLOR, 24, "WeatherModule\nデータを取得できません", x+w/2, y+h/2);
  }
  /* ここまで変更 */
}

ここでネットワークの接続を切ってみてください。
実行してみると、下図のようになります。

0003.png

(表示が確認できたら、ネットワークに再接続してください。)
これで、データが取得できたときのデータ表示と、取得できなかったときの表示が実装できました。
次は、WeatherRModuleの背景を実装します。

モジュール背景の作成準備

下の背景画像を使い、

background.jpg

下のスクリーンショットのようなモジュール背景にします。

WeatherRModule

モジュールの背景は、GridModuleやPlaceholderModuleのときと同様に、PGraphicsを用いて実装します。
モジュール背景を保存しておく変数を用意します。

DigitalSignage.pde
/* 略 */

// WeatherRModule
final float LATITUDE = 41.81469;
final float LONGITUDE = 140.75722;
boolean isUpdatedWeather = false;
PImage weatherIcon;
float temperature = 0.0;
int humidity = 0;
String weatherString = "";
PGraphics weatherBackground; /* 追加 */

/* 略 */

次に、weatherBackgroundを生成するための処理を実装します。
weatherBackgroundは、新しい関数initializeRModuleBackground()内で初期化します。

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();
}
/* ここまで追加 */

/* 略 */

実行する前に、drawWeatherRModule()内で描画するテキストの色をWHITE_COLORに統一します(BLACK_COLORのままだと、背景と同化して見づらくなってしまうため)。

RM_Weather.pde
void drawWeatherRModule(Area area) {
  RModule module = RModule.Weather;
  Size size = moduleSize(module);
  
  int x = layoutGuideX(area);
  int y = layoutGuideY(area);
  int w = moduleWidth(size);
  int h = moduleHeight(size);
  
  drawText(LEFT, BASELINE, WHITE_COLOR, 32, "現在の天気", x+50, y+50); /* 変更 */
  drawText(LEFT, BASELINE, WHITE_COLOR, 16, "気象データ提供元: OpenWeather(TM)", x+50, y+100); /* 変更 */
  
  if (isUpdatedWeather) {
    drawText(LEFT, BASELINE, WHITE_COLOR, 64, int(temperature)+"℃ / "+humidity+"%", x+50, y+160); /* 変更 */
    drawText(LEFT, BASELINE, WHITE_COLOR, 42, weatherString, x+50, y+260); /* 変更 */
    image(weatherIcon, x+w-h, y+50, h, h);
  } else {
    fill(0, 0, 0, 50);
    noStroke();
    rect(x, y, w, h, MODULE_RECT_ROUND);
    
    drawText(CENTER, CENTER, WHITE_COLOR, 24, "WeatherRModule\nデータを取得できません", x+w/2, y+h/2);
  }
}

/* 略 */

実行してみると、下図のようになります。
背景画像を切り取って表示できましたが、角が四角のままです。
角丸の画像を表示できるようにしたいので、PGraphicsのmaskを使用します。

PGraphicsのmask関数

PGraphicsのmask関数は、画像を図形で切り取るために使います。
例を示します。
下の図は、画像の上に白い角丸四角を重ねています。

211031_165048_01.png

この白い図形を使って、画像をマスクすると下図のようになります。

211031_170108_01.png

白い図形の形通りに画像が切り取られています。
出力に用いたプログラムは下記です。

size(900, 675);

PGraphics pg = createGraphics(900, 675);
pg.beginDraw();
pg.image(loadImage("background.jpg"), 0, 0);
pg.endDraw();
  
PGraphics mask = createGraphics(900, 675);
mask.beginDraw();
mask.noStroke();
mask.fill(255);
mask.rectMode(CENTER);
mask.rect(mask.width/2, mask.height/2, 845, 400, 30);
mask.endDraw();
  
pg.mask(mask);
  
image(pg, 0, 0);

プログラムのうち、

マスク処理の例
pg.mask(mask);

上記の記述で、画像を図形でマスクしています。

モジュール背景をマスクする

PGraphicsのmask関数で説明した時のように、モジュールのサイズに応じた白い図形を使い、画像をマスクします。
まず、「モジュールのサイズに応じた白い図形」を返すための関数sizeToModuleMask()を実装します。

DigitalSignage.pde
/* 略 */

PGraphics sizeToModuleMask(Size size) {
  int w = moduleWidth(size);
  int h = moduleHeight(size);
  PGraphics moduleBackgroundMask = createGraphics(w, h);
  
  moduleBackgroundMask.beginDraw();
  
  moduleBackgroundMask.noStroke();
  moduleBackgroundMask.fill(255);
  moduleBackgroundMask.rect(0, 0, w, h, MODULE_RECT_ROUND);
  
  moduleBackgroundMask.endDraw();
  
  return moduleBackgroundMask;
}

/* 略 */

関数sizeToModuleMask()を使って、モジュール背景を初期化する段階でマスクします。

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) ) ); /* 追加 */
}

/* 略 */

初期化用のinitialize()内で、initializeRModuleBackground()を呼び出すようにします。

Initialize.pde
void initialize() {
  initializeDate();
  initializeImage();
  initializeGrid();
  initializePlaceholder();
  initializeRModuleBackground(); /* 追加 */
  
  isUpdatedWeather = updateWeather();
}

/* 略 */

最後に、背景を描画する処理を実装します。

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

  image(weatherBackground, x, y, w, h); /* 追加 */
  
  drawText(LEFT, BASELINE, WHITE_COLOR, 32, "現在の天気", x+50, y+50);
  drawText(LEFT, BASELINE, WHITE_COLOR, 16, "気象データ提供元: OpenWeather(TM)", x+50, y+100);
  
  if (isUpdatedWeather) {
    drawText(LEFT, BASELINE, BLACK_COLOR, 64, int(temperature)+"℃ / "+humidity+"%", x+50, y+160);
    drawText(LEFT, BASELINE, BLACK_COLOR, 42, weatherString, x+50, y+260);
    image(weatherIcon, x+w-h, y+50, h, h);
  } else {
    fill(0, 0, 0, 50);
    noStroke();
    rect(x, y, w, h, MODULE_RECT_ROUND);
    
    drawText(CENTER, CENTER, WHITE_COLOR, 24, "WeatherModule\nデータを取得できません", x+w/2, y+h/2);
  }
}

ここまで実装できたら、実行して確認してください。
下図のように、モジュールの背景が角丸になっているはずです。

0015.png

最後に

これで現在の天気を表示する「WeatherRModule」が実装できました。
次は、**直近2件のバス時刻表を表示する「BusRModule」**を作ってみましょう。

1
1
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
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?