06 December 2025, Saturday

日没の方位が最も南に近づく。Pythonで年間の変化を計算してみた

自宅から見える日没が、そろそろ一年で最も南に来るようになった。

20251205-sunset.jpg
最近の日没と、だいたいの方位角

日没の方位角が、年間でどれくらい変化があるのか、GoogleのGemini AIに聞いてみた。

20251205-sunset-calc.jpg
大阪の日没時刻と方位角の計算結果

Python ソースコード
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
================================================================================
日没時刻と方位角のグラフ表示スクリプト
================================================================================

概要:
    指定された緯度・経度に基づき、pyephemライブラリを使用して
    一年間の日没時刻(JST)と日没方位角(度)を計算し、Matplotlibでグラフ表示する。

実行環境:
    Python 3.x
    必要なライブラリ: pyephem, numpy, matplotlib
    CUI版として動作し、実行時にグラフウィンドウを開く。

最終更新日: 2025-12-06
作成者: Google Gemini3

================================================================================
"""

import ephem
from datetime import datetime, timedelta
import numpy as np
import matplotlib.pyplot as plt
import sys
from typing import List, Tuple

# =================================================================
# グローバル変数として計算対象の緯度・経度を指定 (ここを編集)
# 例: 大阪 (北緯 34.67 度, 東経 135.5 度)
TARGET_LATITUDE_DEG = 34.67
TARGET_LONGITUDE_DEG = 135.5
# =================================================================

# --- 天文計算関数 ---
def calculate_sunset_data(latitude_deg: float, longitude_deg: float) -> Tuple[List[str], List[float], List[float]]:
    """
    指定された緯度と経度に基づき、一年間の日没時刻と方位角を計算する。

    Args:
        latitude_deg (float): 観測地点の緯度(度)。北緯は正(+)、南緯は負(-)。
        longitude_deg (float): 観測地点の経度(度)。

    Returns:
        Tuple[List[str], List[float], List[float]]:
            計算結果を格納した3要素のタプル。要素は以下の通り。
            1. 日付のリスト (List[str]): "MM/DD" 形式の日付文字列リスト。
            2. 日没時刻のリスト (List[float]): JSTでの日没時刻(時間+小数)のリスト。
            3. 日没方位角のリスト (List[float]): 北を0/360度とする方位角(度)のリスト。
            ※ 白夜・極夜で日没がない日は NaN (np.nan) が格納される。
    """
    # 観測地点を設定
    obs = ephem.Observer()
    # ephemは文字列形式の緯度・経度を要求するため変換
    obs.lat = str(latitude_deg)
    obs.lon = str(longitude_deg)

    dates = []
    sunset_times_hours = []
    sunset_azimuths = []

    # JSTはUTC+9時間
    JST_OFFSET_HOURS = 9.0

    # 1年間の日付を生成(来年の1月1日から366日分)
    start_date = datetime(datetime.now().year + 1, 1, 1)

    for i in range(366):
        current_date = start_date + timedelta(days=i)
        obs.date = current_date

        sun = ephem.Sun()  # type: ignore[reportAttributeAccessIssue]

        try:
            # 日没時刻をUTCで計算
            sunset_time_ephem = obs.next_setting(sun)

            # datetimeオブジェクトに変換(UTCとして扱う)
            sunset_time_utc = sunset_time_ephem.datetime()

            # 日付のリストに追加 (月/日形式)
            dates.append(current_date.strftime('%m/%d'))

            # 日没時の太陽の方位角を計算
            obs.date = sunset_time_ephem
            sun.compute(obs)
            azimuth_rad = sun.az
            azimuth_deg = np.degrees(azimuth_rad)
            sunset_azimuths.append(azimuth_deg)

            # 時刻を「時間(小数)」で表現
            sunset_hour_utc = (
                sunset_time_utc.hour +
                sunset_time_utc.minute / 60.0 +
                sunset_time_utc.second / 3600.0
            )

            # --- JST への変換 ---
            # JST時刻 = UTC時刻 + 9時間
            jst_sunset_hour = sunset_hour_utc + JST_OFFSET_HOURS

            # 24時を超えた場合は翌日の時刻として扱う (例: 25.0 -> 1.0)
            if jst_sunset_hour >= 24.0:
                jst_sunset_hour -= 24.0

            sunset_times_hours.append(jst_sunset_hour)

        except (ephem.AlwaysUpError, ephem.NeverUpError):
            # 白夜または極夜
            dates.append(current_date.strftime('%m/%d'))
            sunset_times_hours.append(np.nan)
            sunset_azimuths.append(np.nan)

    return dates, sunset_times_hours, sunset_azimuths

# --- グラフ描画関数 ---


def plot_data(latitude: float, dates: List[str], times: List[float], azimuths: List[float]) -> None:
    """
    計算結果をMatplotlibでプロットし、表示する。

    Args:
        latitude (float): グラフ表示対象の緯度(度)。
        dates (List[str]): グラフのX軸に使用する日付(月/日形式の文字列)のリスト。
        times (List[float]): 各日付の日没時刻(時間+小数:例 18.5)のリスト。
        azimuths (List[float]): 各日付の日没方位角(度)のリスト。

    Returns:
        None: グラフを描画し、表示するのみで、値を返さない。
    """
    fig = plt.figure(figsize=(12, 8))

    # 1つ目のサブプロット: 日没時刻
    ax1 = fig.add_subplot(211)
    ax1.plot(times, color='b')
    ax1.set_title(f"緯度 {latitude}°、経度 {TARGET_LONGITUDE_DEG}° における日没時刻と方位角(一年間)", fontsize=16)
    # ラベルを JST に変更
    ax1.set_ylabel("日没時刻 (時, JST)")
    ax1.grid(True)

    # Y軸の時刻フォーマットを調整 (例: 18.5 -> 18:30)
    from matplotlib.ticker import FuncFormatter

    def time_formatter(y, pos):
        h = int(y)
        m = int((y - h) * 60)
        return f"{h:02d}:{m:02d}"
    ax1.yaxis.set_major_formatter(FuncFormatter(time_formatter))

    # X軸のラベル設定(日付)
    num_ticks = 12
    step = len(dates) // num_ticks
    tick_indices = np.arange(0, len(dates), step)
    tick_labels = [dates[i] for i in tick_indices]

    ax1.set_xticks(tick_indices)
    ax1.set_xticklabels(tick_labels, rotation=45, ha='right')
    ax1.tick_params(labelbottom=False)

    # 2つ目のサブプロット: 日没方位角
    ax2 = fig.add_subplot(212, sharex=ax1)
    ax2.plot(azimuths, color='r')
    ax2.set_ylabel("日没方位角 (度)")
    ax2.set_xlabel("月/日")
    ax2.grid(True)

    ax2.set_yticks(np.arange(240, 310, 10))
    ax2.set_ylim(230, 310)

    ax2.set_xticks(tick_indices)
    ax2.set_xticklabels(tick_labels, rotation=45, ha='right')

    # レイアウト調整と描画
    fig.tight_layout()
    plt.show()


# --- メイン処理 ---
if __name__ == "__main__":

    latitude = TARGET_LATITUDE_DEG
    longitude = TARGET_LONGITUDE_DEG

    # 入力値の確認
    if not (-90 <= latitude <= 90):
        print(f"エラー: 緯度 {latitude}° は -90 から 90 の間で入力してください。", file=sys.stderr)
        sys.exit(1)
    if not (122.95 <= longitude <= 153.98):
        print("--- 警告 ---", file=sys.stderr)
        print(f"経度 {longitude}° は日本の領土の標準的な範囲外です。", file=sys.stderr)
        # 警告に留め、処理は続行する

    print(f"緯度 {latitude}°、経度 {longitude}° の日没データを計算中...")
    dates, times, azimuths = calculate_sunset_data(latitude, longitude)

    if not dates:
        print("エラー: 計算に失敗しました。", file=sys.stderr)
        sys.exit(1)

    print("計算完了。グラフを表示します。(ウィンドウがポップアップします)")
    plot_data(latitude, dates, times, azimuths)