Pythonで少し詳しいお天気情報をLINEに通知する (2)

[前回](https://qiita.com/seigo-pon/items/7827bbf7cf6792df265a) の続きです。

地域IDを取得するクラスを作ったので、それを使ってお天気情報とりたいですよね。という話。

# お天気情報を取得する
[WeatherHacks](http://weather.livedoor.com/weather_hacks/webservice) を使って安定の取得です。
APIからデータ取ってきて辞書にする部分は、すでにみなさん書かれているので割愛です。

# お天気情報を加工する
さてさて、せっかくとってきたデータ、扱いやすいようにクラスにしてみます。
個人的にこのAPIでは以下の部分が使えるかな、と思いました。

![description.png](https://qiita-image-store.s3.amazonaws.com/0/7690/5b44649d-3c48-bcfc-02bc-6d2204887bfc.png)

![forecast.png](https://qiita-image-store.s3.amazonaws.com/0/7690/a8ac88d3-d90c-9171-2e80-ac6dde8b188a.png)

下の方は普通に日毎の予報情報ですね。
上のは、天気予報でよくある概況の説明です。

こんな感じのが取得できるようですわ。

```json
"description" : {
      "text" : " 九州北部地方は、高気圧に覆われて晴れています。\n\n 29日は、九州北部地方では、高気圧に覆われて晴れますが、気圧の谷の\n影響で、昼過ぎから次第に曇りとなるでしょう。\n\n 30日は、気圧の谷の影響ではじめ曇りますが、昼頃からは高気圧に覆わ\nれて概ね晴れるでしょう。\n\n 波の高さは、九州北部地方の沿岸の海域では、29日は1.5メートル、\n30日は1メートルでしょう。豊後水道では、29日と30日は1メートル\nでしょう。\n 福岡県の内海では、29日と30日は0.5メートルでしょう。",
      "publicTime" : "2013-01-29T10:37:00+0900"
   },
```

これらのデータを使って
1. 地域
2. データ取得日時
3. 天気概況
4. 日毎予報
みたいな感じに加工したいと思います。

クラスは以下のを用意しました。

```python
# 日毎お天気クラス
class WeatherDate(object):
    # コンストラクタ
    def __init__(self, date, telop, temp_max, temp_min, image_url):
        self.date = date
        self.telop = telop
        self.temp_max = temp_max
        self.temp_min = temp_min
        self.image_url = image_url

# 全体お天気クラス
class Weather(object):
    # コンストラクタ
    def __init__(self, city, public_time, description, weather_date_list):
        self.city = city
        self.public_time = public_time
        self.description = description
        self.weather_date_list = weather_date_list
```

変換部分です。

```python
# お天気情報を変換
def serializeWeather(city, weather_json):
    # 詳細
    public_time = ""
    description = ""
    if weather_json["description"]:
        # 2018-03-12T10:31:00+0900
        time = weather_json["description"]["publicTime"].encode("utf-8")
        date = datetime.strptime(time.replace("+0900", ""), '%Y-%m-%dT%H:%M:%S')
        public_time = date.strftime("%Y/%m/%d %H:%M")

        description = weather_json["description"]["text"].encode("utf-8")

    # 日毎の予報
    weather_date_list = []
    for forecast in weather_json["forecasts"]:
        # どの日
        date = ""
        if forecast["dateLabel"]:
            date = forecast["dateLabel"].encode("utf-8")

        # 予報
        telop = ""
        if forecast["telop"]:
            telop = forecast["telop"].encode("utf-8")

        # 最高気温
        temp_max = ""
        if forecast["temperature"]["max"]:
            temp_max = forecast["temperature"]["max"]["celsius"]

        # 最低気温
        temp_min = ""
        if forecast["temperature"]["min"]:
            temp_min = forecast["temperature"]["min"]["celsius"]

        # 予報画像URL
        image_url = ""
        if forecast["image"]["url"]:
            image_url = forecast["image"]["url"]

        # 日毎お天気
        weather_date = WeatherDate(date, telop, temp_max, temp_min, image_url)
        weather_date_list.append(weather_date)

    # お天気
    weather = Weather(city, public_time, description, weather_date_list)
    return weather
```
`city` は前回で作った`City`クラスを突っ込むイメージです。
`publicTime` の変換がカオスになってますが、`Python 2.7.14` の日時関連が貧弱なので、無理やり文字としてぶった切って変換してます。

```python
# お天気情報を取得
def getWeatherHacks(city_name):

    # 市情報を検索
    city = primary_area.searchCity(city_name)

    if city:
        # 最新に更新
        weather_json = getWeatherHacksJson(city)

        # お天気情報を取得
        weather = serializeWeather(city, weather_json)

        return weather

    return None
```

最終的な関数。
`getWeatherHacksJson` は好きに想像してください。 

# 少し詳細なお天気情報を吐き出す
さて、このクラスを使ってお天気情報を出力できるようにします。

あー、県と市の名前わかるなー
データの取得日がわかるなー
概況がわかるなー
予報日のラベル(今日とか明日とか)がわかるなー
気温がわかるなー

ということで、以下の形を考えてみました。

```
- ダイジェスト
    - 【〇〇県〇〇市】yyyy/MM/dd HH:mm 現在の天気予報です。
    - 【説明】(概況)
- 日毎予報
    - 【(今日とか明日とか)】(予報)
    - 最高○度
    - 最低◯度
```

(ホントは予報に対しての画像URLが取得できるんで、それ使おうかとも思ったんですが、後述の理由により断念いたしました)

各クラスに対して、上記の仕様に沿った出力関数を追加します。

```Weather.py
# ダイジェスト
def digest(self):
    text = "【{0}{1}市】\n{2} 現在の天気予報です。\n".format(self.city.pref_name, self.city.name, self.public_time)

    text += "【説明】\n"
    description_text = ""
    description_list = self.description.splitlines()
    for description in description_list:
        if not description:
            continue
        if description_text:
            description_text += "\n"
        description_text += description

    text += description_text
    return text
```

概況はなぜか変な改行と空行が間に挟まれるので、一応消す努力をしてみてます。
それでも、、、な部分は後述します。

```WeatherDate.py
# 詳細
def yohou(self):
    text = "【{0}】\n".format(self.date)
    text +=  "{0}".format(self.telop)

    temp = ""
    if self.temp_max:
        temp = "最高 {0}度".format(self.temp_max)

    if self.temp_min:
        if temp:
            temp += "\n"
        temp += "最低 {0}度".format(self.temp_min)

    if temp:
        text += "\n" + temp

    if icon:
        return emoji.emojize(unicode(text, "utf_8"), use_aliases=True)
    else:
        return text
```

これらを組み込んで

```python
weather = getWeatherHacks("大阪")

print weather.digest()
for date in ["今日", "明日", "明後日"]:
    print weather.yohou(date)
```

ってやると
>
【大阪府大阪市】
2018/03/17 16:32 現在の天気予報です。
【説明】
 近畿地方は、高気圧に覆われて晴れています。
 今夜の近畿地方は、高気圧に覆われて晴れるでしょう。
 明日の近畿地方は、高気圧に覆われておおむね晴れますが、気圧の谷の接
近により昼過ぎからは次第に雲が広がる見込みです。
【今日】
晴のち曇
最高 18度
最低 6度
【明日】
曇時々雨

って感じで出力されます。

なんかこれだけだと味気なくない？？
ってことで、天気の表現をこの前書いた絵文字にしてみようと思います。

```WeatherDate.py
# クラス変数
sun = ("晴", ":sun:")
cloud = ("曇", ":cloud:")
rain = ("雨", ":umbrella:")
snow = ("雪", ":snowman:")

# 順番に結果を並べる
def __orderTelopType(self, telop, text, indexPrev, telop_type):
    index = telop.find(telop_type[0])
    if index != -1:
        if indexPrev > index:
            text = telop_type[1] + text
        else:
            text += telop_type[1]

    return (text, index)

# 予報を絵文字に変換
def __telopIcon(self, telop):
    text = ""
    indexPrev = -1

    # 含まれていれば絵文字として出力
    for telop_type in [WeatherDate.sun, WeatherDate.cloud, WeatherDate.rain, WeatherDate.snow]:
        (text, index) = self.__orderTelopType(telop, text, indexPrev, telop_type)
        if indexPrev < index:
            indexPrev = index

    return text

# 詳細
def yohou(self, icon):
    text = "【{0}】\n".format(self.date)
    text +=  "{0}".format(self.telop)

    if icon:
        text += self.__telopIcon(self.telop)

    temp = ""
    if self.temp_max:
        temp = "最高 {0}度".format(self.temp_max)

    if self.temp_min:
        if temp:
            temp += "\n"
        temp += "最低 {0}度".format(self.temp_min)

    if temp:
        text += "\n" + temp

    if icon:
        return emoji.emojize(unicode(text, "utf_8"), use_aliases=True)
    else:
        return text
```

`icon` 引数が `True` だと絵文字を追記します。
使用する絵文字は :sunny: :cloud: :umbrella: :snowman: 
順番を守って追記したかったので、ちょっと変な処理入ってますが、割愛。

以下が出力結果です。

>
【大阪府大阪市】
2018/03/17 16:32 現在の天気予報です。
【説明】
 近畿地方は、高気圧に覆われて晴れています。
 今夜の近畿地方は、高気圧に覆われて晴れるでしょう。
 明日の近畿地方は、高気圧に覆われておおむね晴れますが、気圧の谷の接
近により昼過ぎからは次第に雲が広がる見込みです。
【今日】
晴のち曇☀☁
最高 18度
最低 6度
【明日】
曇時々雨☁☂

ちょっといい感じになったでしょうか。

# LINE Notifyで通知する
さあ、データはできたので、あとは出力ですわ。
LINE Notify使います。

LINE Notifyは、ユーザーとかグループに対して直接メッセージを送れるサービスです。
みなさん持ってる個人アカウントで管理します。
冷静に考えると、通知するグループを作る権限が必要なので、不特定多数への通知とかのサービスじゃないんですな。
今回は自分に対してなので良いですが、それ以外でどう有効活用するのかいまいちわかってません。

## トークンの発行
https://notify-bot.line.me/ja/
ここでまずログインします。
PC連携の設定を切っているアカウントはログインできないので注意。

ログイン後、マイページの「トークンを発行する」でトークンが作れます。

`トークン名` が通知のプレフィックスになるので注意してください。
安易にテストとかにすると

>
[テスト] テスト投稿1
[テスト] テスト投稿2

みたいな感じで、ずっとテストテスト言われ続けます。

作成されたトークンはメモるか、環境変数にしてしまいましょう。

## 通知方法
このトークンを `https://notify-api.line.me/api/notify` に対して投げると通知できます。
通知メッセージのキーは `message` です。

以下のような関数を用意しました。

```python
# LINE Notify に通知する
def postLineNotify(token, message="", image_url=""):

    # LINE Notify API URL
    url = "https://notify-api.line.me/api/notify"

    # リクエストパラメータ
    headers = { "Authorization": "Bearer {0}".format(token) }
    payload = {}

    # メッセージ追加
    if message:
        payload["message"] = message

    # 画像追加
    if image_url:
        payload["imageThumbnail"] = image_url
        payload["imageFullsize"] = image_url

    try:
        payload = urllib.urlencode(payload).encode("utf-8")
        req = urllib2.Request(url, payload, headers=headers)
        urllib2.urlopen(req)

    except Exception as e:
        raise e
```

`imageThumbnail` と `imageFullsize` は画像を通知しようと考えてたときの名残りです。
このキーはURLしか受け付けないので注意。

画像も全然可能なのですが、今回はやめました。
理由は **JPEGしか受け付けない** からです。
WeatherHacksがGIFしか持ってなかったので、そこで挫折いたしました。無念。

## お天気情報を通知する
では、上記を使ってお天気情報を実際に通知してみます。

```python
# 最新の天気予報を取得
weather = getWeatherHacks("大阪")

# ダイジェストをまず通知
postLineNotify(line_token, message=weather.digest())

# 今日、明日、明後日を通知
for date in ["今日", "明日", "明後日"]:
    yohou = weather.yohou(date, icon=True).encode("utf_8")
    if yohou:
        postLineNotify(line_token, message=yohou)
```
以下が実際の通知です。

![IMG_3496.PNG](https://qiita-image-store.s3.amazonaws.com/0/7690/9a311937-08f9-1a74-2da8-144d63ca14a0.png)

![IMG_3497.PNG](https://qiita-image-store.s3.amazonaws.com/0/7690/0ebe1374-95c0-a178-72d1-0ba606529b92.png)

成功いたしました！
無事絵文字も出てます。（結構心配だった）

んん？？
けど概況に変な改行が残ってる？？(接|近になってる)

あ、確かにサンプルもなんか変だ。

>
九州北部地方は、高気圧に覆われて晴れています。\n\n 29日は、九州北部地方では、高気圧に覆われて晴れますが、気圧の谷の\n影響で、昼過ぎから次第に曇りとなるでしょう。\n\n 30日は、気圧の谷の影響ではじめ曇りますが、昼頃からは高気圧に覆わ\nれて概ね晴れるでしょう。\n\n 波の高さは、九州北部地方の沿岸の海域では、29日は1.5メートル、\n30日は1メートルでしょう。豊後水道では、29日と30日は1メートル\nでしょう。\n 福岡県の内海では、29日と30日は0.5メートルでしょう。

ということで、どうもHTML向けなのかなんなのか、テキスト内に折り返しがある所為で少し変な見た目になってしまいました。
気力尽きたので、ここはもうやめときます。

以上で全容となります。
細かいところ端折ってますがご容赦。

で、終わりのはずですがあと一回続きます。