マイコン宇宙講座-大圏コース

今回は、世界地図をディスプレイ上に描き、2つ地点を移動する際の大圏コースをプロットしていきます。天文計算とはあまり関係ありませんが、作成した世界地図は人工衛星追跡でも使用します。使用する世界地図をリストに掲載するのは、量が多いためリストにするには無理がありますので、星座データと同じようにGitHubにあげておきます。世界地図のデータは全部で6個のファイルに分けてありますので、ダウンロードして使用してください。

世界地図を描く

世界地図データファイルを読み込んで世界地図を描いてみましょう。ダウンロードしたデータファイルは、プログラムと同じディレクトリにmapというフォルダを作成し、その中に配置してください。

メインルーチン map_display.py

# 入力したデータの星座確認
from PIL import Image, ImageDraw, ImageFont
import tkinter as tk
import csv
import glob


def drawing_frame(map_color, fonts, draw):
    latitude = [
        [16, '+75'],
        [80, '+60'],
        [128, '+40'],
        [160, '+20'],
        [192, '0'],
        [224, '-20'],
        [256, '-40'],
        [304, '-60'],
        [368, '-75']
    ]

    # フレーム
    draw.rectangle((28, 0, 610, 384), outline=map_color, width=1)

    # 緯度
    for i in range(9):
        y = latitude[i][0]
        la = latitude[i][1]
        drawing_latitude(y, la, draw)

    # 経度
    for lg in range(74, 610, 48):
        draw.line((lg, 0, lg, 5), fill=map_color, width=1)
        draw.line((lg, 379, lg, 384), fill=map_color, width=1)
    draw.text((71, 362), '0', font=fonts, fill=map_color)
    draw.text((162, 362), '60', font=fonts, fill=map_color)
    draw.text((256, 362), '120', font=fonts, fill=map_color)
    draw.text((352, 362), '180', font=fonts, fill=map_color)
    draw.text((496, 362), '300', font=fonts, fill=map_color)
    draw.text((584, 362), '330', font=fonts, fill=map_color)


# 緯度を描く
def drawing_latitude(y, la, draw):
    draw.line((28, y, 33, y), fill=map_color, width=1)
    draw.line((605, y, 610, y), fill=map_color, width=1)
    if la == '0':
        draw.text((9, y - 9), la, font=fonts, fill=map_color)
        draw.text((622, y - 9), la, font=fonts, fill=map_color)
    else:
        draw.text((2, y - 9), la, font=fonts, fill=map_color)
        draw.text((614, y - 9), la, font=fonts, fill=map_color)


# 世界地図を描く
def drawing_map(map_color, draw):
    # 地図データの読み込み
    files = glob.glob('./map/*.csv')

    for file_name in files:
        fp = open(file_name, 'r')
        reader = csv.reader(fp)

        for data in reader:
            x = int(data[0]) * 4
            y = int(data[1]) * 4 + 18

            draw.rectangle((x - 2, y - 2, x + 2, y + 2), fill=map_color)

        fp.close()


root = tk.Tk()
root.resizable(False, False)
root.geometry('644x404')
root.title('マイコン宇宙講座 - 世界地図')

img = Image.new('RGB', (642, 402), (0, 0, 0))
draw = ImageDraw.Draw(img)

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

# フォント名は実行環境に合わせて変更すること
# TrueTypeの等幅フォント名を指定する
fonts = ImageFont.truetype('Ubuntu-L.ttf', 14)

# カラーパレット
map_color = (80, 160, 0)

# フレーム描画
drawing_frame(map_color, fonts, draw)

# 地図プロット
drawing_map(map_color, draw)

# 描画した星座の画像を保存
img.save('./images/map.png')

# 地図表示
photo = tk.PhotoImage(file='./images/map.png')
canvas.create_image(1, 1, image=photo, anchor=tk.NW)

root.mainloop()

実行すると世界地図が表示されます。

次は大圏コースを描いてみましょう。

サブルーチン geocentric_latitude

# 地心緯度
def geocentric_latitude(r1, r2):
    return math.atan((0.9933055 + 1.1e-9 * r2) * math.tan(r1))

サブルーチン geocentric_distance

# 地心距離
def geocentric_distance(r1):
    return 6378.16 * (0.99832707 + 1.67644e-3 * math.cos(2.0 * r1) - 3.52e-6 * math.cos(4.0 * r1))

サブルーチン geography_latitude

# 地理緯度
def geography_latitude(r1):
    r1 = math.atan(math.tan(r1) / 0.9933055)
    r2 = geocentric_distance(r1)

    return r2

メインルーチン m43.py

# my43.py
# マイコン宇宙講座
# 4-2 大圏コースプログラム
from PIL import Image, ImageDraw, ImageFont
import tkinter as tk
import csv
import glob
import math
import lib


def drawing_frame(map_color, fonts, draw):
    latitude = [
        [16, '+75'],
        [80, '+60'],
        [128, '+40'],
        [160, '+20'],
        [192, '0'],
        [224, '-20'],
        [256, '-40'],
        [304, '-60'],
        [368, '-75']
    ]

    # フレーム
    draw.rectangle((28, 0, 610, 384), outline=map_color, width=1)

    # 緯度
    for i in range(9):
        y = latitude[i][0]
        la = latitude[i][1]
        drawing_latitude(y, la, fonts, draw)

    # 経度
    for lg in range(74, 610, 48):
        draw.line((lg, 0, lg, 5), fill=map_color, width=1)
        draw.line((lg, 379, lg, 384), fill=map_color, width=1)
    draw.text((71, 362), '0', font=fonts, fill=map_color)
    draw.text((162, 362), '60', font=fonts, fill=map_color)
    draw.text((256, 362), '120', font=fonts, fill=map_color)
    draw.text((352, 362), '180', font=fonts, fill=map_color)
    draw.text((496, 362), '300', font=fonts, fill=map_color)
    draw.text((584, 362), '330', font=fonts, fill=map_color)


# 緯度を描く
def drawing_latitude(y, la, fonts, draw):
    draw.line((28, y, 33, y), fill=map_color, width=1)
    draw.line((605, y, 610, y), fill=map_color, width=1)
    if la == '0':
        draw.text((11, y - 7), la, font=fonts, fill=map_color)
        draw.text((622, y - 7), la, font=fonts, fill=map_color)
    else:
        draw.text((5, y - 7), la, font=fonts, fill=map_color)
        draw.text((614, y - 7), la, font=fonts, fill=map_color)


# 世界地図を描く
def drawing_map(map_color, draw):
    # 地図データの読み込み
    files = glob.glob('./map/*.csv')

    for file_name in files:
        fp = open(file_name, 'r')
        reader = csv.reader(fp)

        for data in reader:
            x = int(data[0]) * 4
            y = int(data[1]) * 4 + 18

            draw.rectangle((x - 2, y - 2, x + 2, y + 2), fill=map_color)

        fp.close()


# 大圏コースプロット
def plotting_course(ob, lg, la, pa, de, ds, ie, K, fonts, draw):
    for i in range(1, 3):
        lm = lg[i]
        fi = la[i]
        x = 6 + K[3] * (lm + 0.523599) * 0.4
        if x > 150:
            x = x - 150 + 6
        fo = abs(fi)
        y = 21.7007 * math.log(math.tan(math.pi / 4.0 + fo / 2.0))
        if fi > 0:
            y = -y
        if fi * K[3] > 73:
            y = -44
        if fi * K[3] < -73:
            y = 44
        x = int(x / 2)
        y = int(12.5 + y / 4)
        if x < 40:
            draw.text((x * 8, (y + 1) * 16), ob[i], font=fonts)
        if x >= 40:
            draw.text(((x - 7) * 8, (y + 1) * 16), ob[i], font=fonts)
        draw.ellipse((x * 8, y * 16, x * 8 + 10, y * 16 + 10))
        draw.ellipse((x * 8 + 2, y * 16 + 2, x * 8 + 8, y * 16 + 8), fill=(255, 255, 255))
        # draw.text((x * 8, y * 16), '*', font=fonts)

    # 大円のプロット
    if pa > math.pi and lg[2] > math.pi:
        lg[2] = lg[2] - (2.0 * math.pi)
    ii = int(abs((lg[1] - lg[2]) / (15.0 / K[3])))
    iv = 15.0 / K[3]
    sg = 1
    if pa > math.pi:
        sg = -1
    lm = lg[1] + iv * sg
    for i in range(ii):
        fi = math.atan(math.tan(ie) * math.sin(lm - de))
        x = 6 + K[3] * (lm + 0.523599) * 0.4
        fo = abs(fi)
        y = 21.7007 * math.log(math.tan(math.pi / 4.0 + fo / 2.0))
        if fi > 0:
            y = -y
        if fi * K[3] > 73:
            y = -44
        if fi * K[3] < -73:
            y = 44
        x = int(x / 2)
        y = int(12.5 + y / 4)
        draw.text((x * 8, y * 16), '*', font=fonts)
        lm += iv * sg


# メイン
obs = [0, 0, 0]
lg = [0, 0, 0]
la = [0, 0, 0]

# 起点都市と終点都市の情報を入力
while 1:
    obs[1] = input('\n起点都市の名前 ? ')
    lg[1] = float(input('その経度 ? '))
    la[1] = float(input('その緯度 ? '))
    lg[1] /= lib.K[3]
    la[1] /= lib.K[3]
    if lg[1] < 0:
        lg[1] += 2.0 * math.pi

    obs[2] = input('\n距離を求めたい都市の名前 ? ')
    lg[2] = float(input('その経度 ? '))
    la[2] = float(input('その緯度 ? '))
    lg[2] /= lib.K[3]
    la[2] /= lib.K[3]
    if lg[2] < 0:
        lg[2] += 2.0 * math.pi

    check_lg1 = abs(lg[1] - lg[2]) < 1.0e-4
    check_lg2 = abs(lg[2] - lg[1] - math.pi) < 1.0e-4
    if check_lg1 and check_lg2:
        print('\nERROR: この大円は計算できません!\n')
        continue
    else:
        break

# 2都市間の距離と位置角の計算
ss = math.cos(la[2]) * math.sin(lg[2] - lg[1])
cc = math.cos(la[1]) * math.sin(la[2]) - math.sin(la[1]) * math.cos(la[2]) * math.cos(lg[2] - lg[1])

tt = lib.quadrant(ss, cc)

pa = tt
cc = math.sin(la[1]) * math.sin(la[2]) + math.cos(la[1]) * math.cos(la[2]) * math.cos(lg[2] - lg[1])
ss = ss / math.sin(pa)

tt = lib.quadrant(ss, cc)

dg = tt * lib.K[3]
ds = 111.195 * dg
ss = math.tan(la[1])
cc = (math.tan(la[2]) - math.tan(la[1]) * math.cos(lg[2] - lg[1])) / math.sin(lg[2] - lg[1])

tt = lib.quadrant(ss, cc)

de = lg[1] - tt
ss /= math.sin(lg[1] - de)
ie = math.atan(ss)
print('\n')
print(obs[1], ' から ', obs[2], ' までの距離')
print('     距離  = %8.2f Km' % (ds))
print('     位置角 = %8.2f' % (pa * lib.K[3]))
print('\n')

std = input('PRESS ANY KEY ')

# ウィンドウとキャンパス
root = tk.Tk()
root.resizable(False, False)
root.geometry('644x404')
root.title('マイコン宇宙講座 - 大圏コース・プログラム')

img = Image.new('RGB', (642, 402), (0, 0, 0))
draw = ImageDraw.Draw(img)

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

# フォント名は実行環境に合わせて変更すること
fonts1 = ImageFont.truetype('TakaoPGothic.ttf', 13)
fonts2 = ImageFont.truetype('TakaoPGothic.ttf', 20)

# カラーパレット
map_color = (80, 160, 0)

# フレーム描画
drawing_frame(map_color, fonts1, draw)

# 地図プロット
drawing_map(map_color, draw)

# 大圏コースプロット
plotting_course(obs, lg, la, pa, de, ds, ie, lib.K, fonts2, draw)
result = 'From ' + obs[1] + ' to ' + obs[2] + '    Distance = %7.2f Km   Position Angle = %5.2f' % (ds, pa * lib.K[3])
draw.text((70, 340), result, fill=map_color, font=fonts1)

# 描画した星座の画像を保存
img.save('./images/map.png')

# 地図表示
photo = tk.PhotoImage(file='./images/map.png')
canvas.create_image(1, 1, image=photo, anchor=tk.NW)

root.mainloop()

例題 東京とニューヨークの2点間の位置角と距離を求め、そのコースを表示してみよう。

起点都市の名前 ? Tokyo
その経度 ? 139.745
その緯度 ? 35.654

距離を求めたい都市の名前 ? NewYork
その経度 ? -73.87
その緯度 ? 40.77


Tokyo  から  NewYork  までの距離
     距離  = 10849.89 Km
     位置角 =    25.02


PRESS ANY KEY
東京-ニューヨーク間の位置角・距離とそのコース
東京-ニューヨーク間の位置角・距離とそのコース