マイコン宇宙講座-惑星直列シミュレーション

太陽系惑星が直列する年を探します。惑星が直列する天文現象はめずらしく、直近では1982年に90度範囲内に全惑星が収まる現象がおきました。ただ、これが惑星直列になるかどうかは議論の余地があったそうです。計算による惑星直列では1128年4月7日から5月13日の間、39度の範囲内に水星から海王星まで惑星が並ぶ現象が起きたそうです。実際に確認してみると、以下のようになりました。冥王星を除いて狭い範囲内に惑星が並んでいますね。

1128年4月7日の惑星直列現象
1128年4月7日の惑星直列現象

おなじように水星から海王星が並ぶのは2161年5月と計算されています。ただし、収まる範囲が68度と広めです。

メインルーチン m34.py

このプログラムでは、1年毎の惑星の位置を描画し、./images/planetsというフォルダの中に画像を保存します。途中経過をみながらということになっていません。そのため、planets-slide.pyを作成し、実行することで確認できるようにしました。描画する年数は10年としています。年数を変更する場合は、プログラム中の変数yearsに代入している値を変更します。ただし、10000などとすると、10000枚の画像ファイルが作成されてしまいますので注意してください。

※実行する前にimagesフォルダを作成し、その中にplanetsというフォルダを作成しておいてください。
※プログラムのコメントにも書かれているようにpython3-pil.imagetkが必要です。
※追記 planets-slide.pyの機能はメインルーチンに組み込みました。

# m34.py
# マイコン宇宙講座
# 3-4 太陽系の惑星位置表示プログラム
# 惑星直列シミュレーション
#
# 要インストール
# sudo apt install python3-pil.imagetk

from PIL import Image, ImageDraw, ImageFont
from tkinter.constants import SOLID
import tkinter as tk
import math
import glob
import lib


M2PI = 2.0 * math.pi

# 最初のの画像を表示
def display_eclipse():
    global count, files

    drawing_image_file(files[count])


# ひとつ前の画像を表示
def display_eclipse_prev():
    global count, files

    count -= 1
    if count < 0:
        count = 0
    drawing_image_file(files[count])


# 次の画像を表示
def display_eclipse_next():
    global count, file_number, files

    count += 1
    if count > file_number - 1:
        count = file_number - 1
    drawing_image_file(files[count])

    
def drawing_image_file(picture):
    global photo

    # 画像の表示
    photo = tk.PhotoImage(file=picture)
    canvas.create_image(1, 1, image=photo, anchor=tk.NW)


# 太陽系を表示する
def display_solor_system():
    global photo, tk, file_number, count, files

    std = editbox.get()
    dy, dt = std.split(',')
    dy = float(dy)
    dt = float(dt)

    jd, yy, mm, dd, hh, ms, ss = lib.mjd(dy, dt)

    # 最大何年分を表示するか指定する
    years = 10

    # 指定された年数分を表示
    for fn in range(years + 1):
        yy, mm, dd = lib.jdate(jd, lib.T)
        drawing_solor_system(jd, yy, mm, dd, lib.T, fn)
        jd += 365.0

    prev_button['state'] = tk.NORMAL
    next_button['state'] = tk.NORMAL

    # 保存された画像を表示
    # imagesフォルダ内にある画像ファイル数を取得する
    files = sorted(glob.glob('./images/planets/*.png'))
    file_number = len(files)
    count = file_number - 1

    # 保存された最後の画像を表示
    s = str(len(files) - 1)
    sfn = s.zfill(5)
    photo = tk.PhotoImage(file='./images/planets/solor' + sfn + '.png')
    canvas.create_image(1, 1, image=photo, anchor=tk.NW)

    # テキストボックスの中身をクリア
    editbox.delete(0, tk.END)

    # 日付と時刻の入力欄にフォーカスをセット
    editbox.focus_set()


# 太陽系を描く
def drawing_solor_system(jd, yy, mm, dd, T, fn):
    img = Image.new('RGB', (642, 402), (0, 0, 0))
    draw = ImageDraw.Draw(img)

    # フォント名は実行環境に合わせて変更すること
    # TrueTypeの等幅フォント名を指定する
    font1 = ImageFont.truetype('TakaoGothic.ttf', 16)
    font2 = ImageFont.truetype('TakaoGothic.ttf', 12)

    # 惑星のカラーパレット
    planet_color = [
        '#ff0000',
        '#FFD7A0',
        '#FAAF3C',
        '#37AAE1',
        '#FF5050',
        '#B9BEB4',
        '#C8A05F',
        '#A0C8FA',
        '#3232E1',
        '#E6B96E'
    ]

    # 画面補正値
    pwx = 4
    pwy = 4

    # 画面上の太陽の位置
    mx = 412
    my = 200
    sx = 110
    sy = 110

    # フレームを描く
    draw.rectangle((0, 0, 640, 400))
    draw.line((224, 0, 224, 400))
    draw.line((0, 220, 224, 220))

    # Sun
    draw.ellipse((mx - 4, my - 4, mx + 4, my + 4), fill=(255, 0, 0))  # メイン画面
    draw.ellipse((sx - 4, sy - 4, sx + 4, sy + 4), fill=(255, 0, 0))  # サブ画面

    # 春分点の方向(メイン画面)
    for ln in range(418, 620, 10):
        draw.line((ln, 200, ln + 5, 200))
    draw.line((625, 200, 620, 195))
    draw.line((625, 200, 620, 205))

    # 春分点の方向(サブ画面)
    for ln in range(114, 210, 5):
        draw.line((ln, 110, ln + 2, 110))
    draw.line((214, 110, 209, 105))
    draw.line((214, 110, 209, 115))

    # タイトルの表示
    draw.text((400, 380), 'SOLOR SYSTEM', font=font1)

    # 年月日の表示
    datestr = str('%4d年 %2d月 %2d日' % (yy, mm, dd))
    draw.text((65, 230), datestr, font=font2)

    # 惑星名とシンボルを描画
    draw.text((10, 270), 'P', font=font2)
    draw.text((10, 285), 'L SUN------(   ) MERCURY------(   )', font=font2)
    draw.text((10, 300), 'A VENUS----(   ) EARTH--------(   )', font=font2)
    draw.text((10, 315), 'N MARS-----(   ) JUPITER------(   )', font=font2)
    draw.text((10, 330), 'E SATURN---(   ) URANUS-------(   )', font=font2)
    draw.text((10, 345), 'T NEPTUNE--(   ) PLUTO--------(   )', font=font2)
    draw.text((10, 360), 'S', font=font2)

    # 太陽
    drawing_planet_symbol(0, 91, 291, planet_color[0], draw)
    # 水星
    drawing_planet_symbol(1, 204, 291, planet_color[1], draw)
    # 金星
    drawing_planet_symbol(2, 91, 306, planet_color[2], draw)
    # 地球
    drawing_planet_symbol(3, 204, 306, planet_color[3], draw)
    # 火星
    drawing_planet_symbol(4, 91, 321, planet_color[4], draw)
    # 木星
    drawing_planet_symbol(5, 204, 321, planet_color[5], draw)
    # 土星
    drawing_planet_symbol(6, 91, 336, planet_color[6], draw)
    # 天王星
    drawing_planet_symbol(7, 204, 336, planet_color[7], draw)
    # 海王星
    drawing_planet_symbol(8, 91, 351, planet_color[8], draw)
    # 冥王星
    drawing_planet_symbol(9, 204, 351, planet_color[9], draw)

    # 惑星の位置計算
    t1 = jd - 33281.92334
    t1 = t1 * (2.737909288e-5 + 1.260132857e-17 * t1)
    t2 = t1 * t1

    for pn in range(1, 10):
        e, m, p, n, i, a, rd = lib.mean_elements(pn, t1, t2)
        ec = e
        mo = M2PI * (m / (M2PI) - int(m / (M2PI)))

        ss, cc, ff = lib.kepler(mo, ec)

        b = a * math.sqrt(1 - ec * ec)
        ss = b * ss
        cc = a * ff

        tt = lib.quadrant(ss, cc)

        v = tt
        r = math.sqrt(ss * ss + cc * cc)
        pp = n + p
        vv = v + pp
        r0 = 15
        if pn > 4:
            r0 = 0.9
        rr = r0 * r
        x = rr * math.cos(vv)
        y = rr * math.sin(vv)
        y = -y
        if pn <= 4:
            # 水星-火星はサブ画面へ
            x2 = int((x * pwx) + sx)
            y2 = int((y * pwy) + sy - 4)
        else:
            # 木星-冥王星はメイン画面へ
            x2 = int((x * pwx) + mx)
            y2 = int((y * pwy) + my)

        # 惑星を描く
        drawing_planet_symbol(pn, x2, y2, planet_color[pn], draw)

    # ファイル番号を表示
    drawing_file_number(fn, draw, font1)

    # 画像を保存
    # 必ずPNG形式で保存
    s = str(fn)
    sfn = s.zfill(5)
    img.save('./images/planets/solor' + sfn + '.png')


# 惑星のシンボルを描く
def drawing_planet_symbol(p, x, y, color, draw):
    draw.ellipse((x - 4, y - 4, x + 4, y + 4), fill=color)
    if p == 6:
        draw.line((x - 8, y, x + 8, y), fill=color)


# ユリウス日から年月日を返す
def display_date(jd, T, draw, fonts):
    yy, mm, dd = lib.jdate(jd, T)
    # 年月日の表示
    draw.rectangle((65, 380, 209, 395), fill=(0, 0, 0))
    datestr = str('%4d年 %2d月 %2d日' % (yy, mm, dd))
    draw.text((65, 380), datestr, font=fonts)


# 画像の最上部にファイル番号を描く
def drawing_file_number(fn, draw, fonts):
    s = str(fn)
    sfn = s.zfill(5)
    draw.rectangle((550, 5, 638, 15), fill=(0, 0, 0))
    draw.text((550, 5), 'No.: ' + sfn, font=fonts)


# メイン
root = tk.Tk()
root.resizable(False, False)
root.geometry('642x440')
root.title('マイコン宇宙講座 - 惑星直列シミュレーションプログラム')

canvas = tk.Canvas(root, width=643, height=400, bg='black')
canvas.pack(anchor=tk.NW)

# imagesフォルダ内にある画像ファイルを取得する
files = sorted(glob.glob('./images/planets/*.png'))

# カウンターとファイルの数を取得
file_number = len(files)

label = tk.Label(root, text='DATE AND TIME(JST):')
label.place(x=10, y=414)

editbox = tk.Entry(root, relief=SOLID)
editbox.place(x=155, y=412, width=140, height=24)

# 前の画像を表示
prev_button = tk.Button(root, text='Prev', width=6, relief=SOLID, cursor='hand1', command=display_eclipse_prev)
prev_button.place(x=324, y=409)

# 次の画像を表示
next_button = tk.Button(root, text='Next', width=6, relief=SOLID, cursor='hand1', command=display_eclipse_next)
next_button.place(x=402, y=409)

calc_button = tk.Button(root, text='計算', width=6, relief=SOLID, cursor='hand1', command=display_solor_system)
calc_button.place(x=480, y=409)

button = tk.Button(root, text='閉じる', width=6, relief=SOLID, cursor='hand1', command=root.destroy)
button.place(x=558, y=409)

# 日付と時刻の入力欄にフォーカスをセット
editbox.focus_set()

# 最初の画像を表示
if file_number != 0:
    count = 0
    display_eclipse()
    prev_button['state'] = tk.NORMAL
    next_button['state'] = tk.NORMAL
else:
    prev_button['state'] = tk.DISABLED
    next_button['state'] = tk.DISABLED

root.mainloop()

例題 2022年7月1日9時00分00秒(JST)から10年間、惑星が直列するか確認してみよう。

DATE AND TIME(JST)に20000101,090000と入力して、[計算]をクリックすると、10年間の惑星の位置が描かれた画像が作成されます。作成された画像は[Prev]または[Next]をクリックして表示することができます。ただし、画像ファイルがない場合は、[Prev]および[Next]をクリックすることはできません。

惑星直列シミュレーションの実行結果
惑星直列シミュレーションの実行結果