Python + OpenMeteo + FreeSimpleGUIで気象情報を表示する

PythonでGUIライブラリを使ってみたかったので、FreeSimpleGUIライブラリを使って簡単なプログラムの作成をするべくGeminiに聞いてみた。

ライブラリをインストールする

新たにインストールするライブラリ

  • requests
    Open-Meteoにリクエストを送るために必要
  • FreeSimpleGUI(旧PySimpleGUI)
    GUIを構築するために必要

仮想環境にインストールする場合

$ pip3 install requests FreeSimpleGUI

通常の環境にインストールする場合

$ pip3 install requests FreeSimpleGUI --break-system-packages

※通常の環境にインストールする場合にはLinux Mintのシステム保護機能を無視するための --break-system-packages フラグを付ける必要がある。(自己責任で)

プログラムの解説

プログラムの解説はGoogle Geminiに生成してもらったものを若干修正した。


プログラムは大きく分けて**「①画面の作成と待機(メインループ)」「②データ取得と変換(裏側の処理)」「③画面の書き換え(表示の更新)」**の3つの要素で構成されています。

1. プログラムの全体的な動作の流れ

スクリプトを起動(python3 shizuoka_weather.py)してから終了するまでの流れは以下の通りです。

起動と初期設定:

プログラムが実行されると、まず一番下の if __name__ == ‘__main__’: から main() 関数が呼び出されます。

画面(GUI)の構築:

main() の中で、タイトルやテキスト、ボタンの配置(レイアウト)が定義され、ウィンドウが生成されます。

初回のデータ取得:

ウィンドウが表示されると同時に update_display(window) が1回だけ自動で呼ばれ、現在の天気を取得して画面に表示します。

イベントループ(待機状態):

while True: のループに入り、ユーザーが何か操作をするまでプログラムは待機します。

ボタン操作による分岐:

「最新に更新」ボタンを押す ➔ 再度 update_display(window) が呼ばれ、最新データを取得して画面を書き換えます。

「終了」ボタン、またはウィンドウの「✕」を押す ➔ ループを抜け出してプログラムを終了します。

2. 各関数の役割と内容

スクリプト内に定義した主要な関数の内容です。

① get_weather_data() (気象データの取得)

このプログラムの「心臓部」です。インターネットを通じてOpen-MeteoのAPIから静岡市の気象データを引っ張ってきます。

通信先: url = “https://api.open-meteo.com/v1/forecast”

パラメータ: 静岡市の緯度・経度、欲しいデータ(気温、湿度、気圧、天候コード)を指定します。

キャッシュ対策の工夫: nocache: time.time() をつけることで、「毎回違うURL(パラメータ)」でリクエストを送り、Linux環境やネットワークが古いデータ(キャッシュ)を返すのを防いでいます。

結果の返却: 取得したJSONデータから必要な部分を抜き出し、辞書型(dictionary)にまとめて返します。

② get_weather_description(code) (天候コードの翻訳)

APIは天候を「0」や「61」といったWMO(世界気象機関)の数字コードで返してきます。この関数は、その数字を「快晴」や「弱い雨」といった日本語のテキストに変換する「辞書」の役割を果たします。

③ update_display(window) (画面表示の更新処理)

ボタンが押されたときや起動時に、データの取得から画面の書き換えまでを一手に引き受ける「現場監督」です。

通信時刻の記録: まず、プログラムが動いた瞬間のPCの現在時刻を取得し、「最終通信確認」として画面を更新します(これにより、フリーズしていないことが分かります)。

データの受け取り: get_weather_data() を呼び出して、先ほどの気象データを受け取ります。

画面の書き換え: 受け取ったデータ(API側のデータ時刻、天候、気温、湿度、気圧)を、FreeSimpleGUIの機能(window[‘-KEY-‘].update(…))を使って、画面の該当箇所にサクッと上書きします。

④ main() (メイン処理とGUI設定)

全体の土台となる部分です。

sg.theme(‘SystemDefaultForReal’) でLinux Mintの標準デザインに馴染む色合いを設定。

layout = […] のリスト構造で、「どこに」「どんな文字やボタンを置くか」を設計図のように記述しています。各項目に付けた key=’-TEMP-‘ などの名前が、あとでデータを書き換えるための「目印」になっています。

今回のスクリプトの優れたポイント

APIの仕様を理解した設計: 「API側は15分間隔でしかデータが更新されない」という特性を考慮し、「APIのデータ時刻(鮮度)」と「アプリが通信した時刻(動作確認)」を明確に分けたことで、ユーザーが混乱しない親切な作りになっています。

エラーに強い: try…except 構文を入れているため、もし一時的にインターネットが切れていても、プログラムが突然クラッシュ(強制終了)せず、「エラー」や「データ取得失敗」として優しく知らせてくれます。

Pythonプログラム

import requests
import FreeSimpleGUI as sg
from datetime import datetime
import time

# 静岡市の緯度と経度
LATITUDE = 34.9756
LONGITUDE = 138.3828

def get_weather_data():
    url = "https://api.open-meteo.com/v1/forecast"
    params = {
        "latitude": LATITUDE,
        "longitude": LONGITUDE,
        "current": "temperature_2m,relative_humidity_2m,surface_pressure,weather_code",
        "timezone": "Asia/Tokyo",
        "nocache": time.time() 
    }
    
    try:
        response = requests.get(url, params=params, timeout=10)
        response.raise_for_status() 
        data = response.json()
        current = data.get("current", {})
        
        # APIが持っている「データの有効時刻」
        raw_time = current.get("time", "")
        if raw_time:
            dt = datetime.fromisoformat(raw_time)
            api_time = dt.strftime("%H:%M")
        else:
            api_time = "--:--"

        return {
            "api_time": api_time,
            "temp": current.get("temperature_2m", "--"),
            "hum": current.get("relative_humidity_2m", "--"),
            "press": current.get("surface_pressure", "--"),
            "code": current.get("weather_code", -1)
        }
    except Exception as e:
        print(f"Error: {e}")
        return None

def get_weather_description(code):
    weather_map = {
        0: "快晴", 1: "晴れ", 2: "一部曇り", 3: "曇り",
        45: "霧", 48: "着氷性の霧", 51: "弱い霧雨", 53: "霧雨", 55: "強い霧雨",
        61: "弱い雨", 63: "雨", 65: "強い雨", 80: "にわか雨", 95: "雷雨"
    }
    return weather_map.get(code, f"不明({code})")

def update_display(window):
    # 1. まず「アプリが通信を試みた時刻」を即座に更新
    exec_time = datetime.now().strftime("%H:%M:%S")
    window['-EXEC_TIME-'].update(f"最終通信確認: {exec_time}")
    window.refresh()
    
    # 2. データを取得
    data = get_weather_data()
    
    if data:
        # 3. 各項目を更新
        window['-API_TIME-'].update(f"{data['api_time']} 時点のデータ")
        window['-WEATHER-'].update(get_weather_description(data['code']))
        window['-TEMP-'].update(f"{data['temp']} ℃")
        window['-HUMIDITY-'].update(f"{data['hum']} %")
        window['-PRESSURE-'].update(f"{data['press']} hPa")
    else:
        sg.popup_error("データの取得に失敗しました。ネット接続を確認してください。")

def main():
    sg.theme('SystemDefaultForReal') # Linux Mintのデスクトップに馴染む色調

    layout = [
        [sg.Text('静岡市 気象情報', font=('Helvetica', 16, 'bold'))],
        [sg.Text('通信状態:', size=(10, 1)), sg.Text('---', key='-EXEC_TIME-', text_color='darkgreen')],
        [sg.HorizontalSeparator()],
        
        # API側のデータ時刻(ここが15分間隔で変わる部分)
        [sg.Text('データ時刻:', size=(10, 1)), sg.Text('--:-- 時点のデータ', key='-API_TIME-', font=('Helvetica', 10, 'italic'))],
        
        [sg.Text('天候:', size=(10, 1)), sg.Text('--', key='-WEATHER-', size=(20, 1), font=('Helvetica', 12, 'bold'))],
        [sg.Text('気温:', size=(10, 1)), sg.Text('-- ℃', key='-TEMP-', size=(20, 1))],
        [sg.Text('湿度:', size=(10, 1)), sg.Text('-- %', key='-HUMIDITY-', size=(20, 1))],
        [sg.Text('気圧:', size=(10, 1)), sg.Text('-- hPa', key='-PRESSURE-', size=(20, 1))],
        
        [sg.HorizontalSeparator()],
        [sg.Button('最新に更新', key='-REFRESH-', button_color=('white', '#2c3e50')), sg.Button('終了')]
    ]

    window = sg.Window('Weather Checker', layout, finalize=True)
    update_display(window)

    while True:
        event, values = window.read()
        if event in (sg.WIN_CLOSED, '終了'):
            break
        if event == '-REFRESH-':
            update_display(window)

    window.close()

if __name__ == '__main__':
    main()