【Bラボデジタルサイネージ12】現在の天気を表示する「WeatherRModule」

![](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」（本記事）**
13. [【Bラボデジタルサイネージ13】直近2件のバス時刻表を表示する「BusRModule」](https://qiita.com/ohayota/items/93d7c9b5328188b3c5b6)
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)

# 前提

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

https://openweathermap.org


# 作るもの

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

天気データを取得できたとき
![0145.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/368192/eb2ab942-78b7-9a54-8d9d-db4fab076433.png)

天気データを取得できなかったとき
![0003.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/368192/103d0d42-c62a-b01a-259c-21194d496349.png)


# 背景画像の準備

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

![background.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/368192/c5983ca6-aad8-0e09-7383-0e9901b409f4.jpeg)

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

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

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

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

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

/* 略 */
```


# WeatherRModule用関数の準備

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

``` java: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の建物のものを例とします。

``` java:DigitalSignage.pde
/* 略 */
boolean isHoliday;

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

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

/* 略 */
```

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

https://www.google.co.jp/maps/place/MIRAI+BASE/@41.8144384,140.7571141,19.4z/data=!3m1!5s0x5f9ef42ee2a39d7b:0xb26a126f683f337c!4m12!1m6!3m5!1s0x5f9ef42ee2a8a8f9:0x465635e7d9334cc8!2sMIRAI+BASE!8m2!3d41.8144606!4d140.7571588!3m4!1s0x5f9ef42ee2a8a8f9:0x465635e7d9334cc8!8m2!3d41.8144606!4d140.7571588?hl=ja

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

![スクリーンショット 2021-10-30 23.48.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/368192/30bf4cd1-ac18-aff9-b4d2-5fa8712c056f.png)

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

https://home.openweathermap.org

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

![スクリーンショット 2021-10-31 0.01.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/368192/8fc90463-d9f0-13f8-4529-5dd83ca2d906.png)

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

![スクリーンショット 2021-10-31 0.05.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/368192/17473fe9-fd77-42a3-87bd-ede93c3a0550.png)

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

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

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

![WeatherRModule](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/368192/8c4dbd70-9cf9-cf2a-4fed-b8331f5555dc.png)

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

| 変数名 | 意味 | 使用場面 |
| :-- | :-- | :-- |
| `weatherIcon` | 天気アイコン | 天気名に対応するアイコン |
| `temperature` | 気温 | 現在の気温（摂氏） |
| `humidity` | 湿度 | 現在の湿度 |
| `weatherString` | 天気名 | 現在の天気を説明する文章 |


``` java: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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/368192/cfafe7ee-221c-bda2-b324-1d80c8829977.png)

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

``` java: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;
}
```

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

``` json:データ例
{
    "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です。）

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

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

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

``` java:例
processing.data.JSONObject current = json.getJSONObject("current");
```

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

``` java:例
processing.data.JSONArray weather = current.getJSONArray("weather");
```

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

``` java:例
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を取り出すときは、下記の処理を使います。

``` java:例
processing.data.JSONObject example = json.getJSONArray("example").getJSONObject(0);
```

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

``` java: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からアイコンを読みこんで、変数に代入する処理を書きます。

``` java: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文で実装すると下記のようになります。

``` java: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()`内に記述すれば実現できます。

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

/* 略 */
```

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

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

    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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/368192/fbc1ffa1-5fa7-67eb-caee-6d9a0d1efa53.png)

![0145.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/368192/eb2ab942-78b7-9a54-8d9d-db4fab076433.png)

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

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

/* 略 */
```

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

https://qiita.com/ohayota/items/f52a10660009794afd9f#モジュールの高さと幅を定める

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

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

``` java:DigitalSignage.pde
/* 略 */
  area7,
  area8
}

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

/* 略 */
```

次に関数を作ります。

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

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

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

``` java: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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/368192/8c4dbd70-9cf9-cf2a-4fed-b8331f5555dc.png)

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

``` java: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()`を呼び出すように変更します。

``` java: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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/368192/0fc55da3-1d9c-4270-3ceb-081b615a3561.png)

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

``` java: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()`を呼び出している箇所を変更します。

``` java:Initialize.pde
void initialize() {
  initializeDate();
  initializeImage();
  initializeGrid();
  initializePlaceholder();
  
  isUpdatedWeather = updateWeather(); /* 変更 */
}
```

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

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

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

``` java: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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/368192/dbe9ecb7-d8ec-3bc9-c7ff-4b3e152ad40f.png)

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


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

下の背景画像を使い、

![background.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/368192/c5983ca6-aad8-0e09-7383-0e9901b409f4.jpeg)

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

![WeatherRModule](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/368192/8c4dbd70-9cf9-cf2a-4fed-b8331f5555dc.png)

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

``` java: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()`内で初期化します。

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

/* 略 */
```

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

``` java: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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/368192/9f979127-7204-3f9a-31a7-e6f6587001cd.png)

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

![211031_170108_01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/368192/eff1b8b3-8311-5d5c-0b33-14903860cbc0.png)

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

``` java:例
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);
```

プログラムのうち、

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

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


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

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

``` java: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()`を使って、モジュール背景を初期化する段階でマスクします。

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

/* 略 */
```

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

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

/* 略 */
```

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

``` java: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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/368192/bbba5465-6c2e-cad1-f346-05afe4c96d17.png)


# 最後に

これで現在の天気を表示する「WeatherRModule」が実装できました。
次は、**[直近2件のバス時刻表を表示する「BusRModule」](https://qiita.com/ohayota/items/93d7c9b5328188b3c5b6)**を作ってみましょう。

https://qiita.com/ohayota/items/93d7c9b5328188b3c5b6