リニューアルしたホームページに、暦、月齢情報を自動で掲載できるようにした。
暦データはいろいろなサイトで閲覧できるので、その情報をスクレイプして転記しようと考えていたが、PyEphemという天文科学計算のプログラムを使うと、自分でも暦データを算出できることが分かった。さらにこのPyEphem astronomy libraryを使ったプログラムが、大阪市立科学館の学芸員 江越さんのホームページに公開・掲載されいたので、それを借用させていただいた。
方法
1、暦データの計算
Python の天文計算ライブラリ PyEphem(ephemeris:天体暦の意)は、観測地の緯度・経度を与えると、その観測点から見える太陽、月、惑星などの軌道や二つの天体の離角などを計算し、日の出や月の出の時刻などを容易に算出することができる凄いプログラムである。
作者はElwood Downey氏だそうだがネット検索しても顔も見えない。世界にはこうした天才的なアマチュアがたくさんいることに改め驚く。
日の出・日の入り、月の出、月の入りプログラム:moonage-write.py
# 飯田の日の出日の入り、月齢計算
# 参考URL:http://www.sci-museum.kita.osaka.jp/~egoshi/astronomy/python/python_moon.html
# 参考URL:https://rhodesmill.org/pyephem/
import ephem
import datetime
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D # 3Dの散布図や等高線プロットなどを描画できるModule
import sys
# 日本語UTF-8のファイルへの書き出しは、write関数。
# ファイルへの書き出しに encoding ='utf-8'を指定
# print("日本語") は、さくらインターネットのDefaultLANGがASCIIのため使えない
######################
# 観測地設定 //Computations for Particular Observers
iida = ephem.Observer()
iida.lat = '35.47' # lattitude by Google
iida.lon = '137.8' # longitude by Google
# 観測値のLocaltime
iida.date = datetime.datetime.utcnow()
d=iida.date
ephem.localtime(d)
# print(ephem.localtime(d))
# 月の計算
moon = ephem.Moon()
moon.compute(iida)
# 次回 月の出・月の入り
next_moonrise = ephem.localtime(iida.next_rising(moon))
next_moonset = ephem.localtime(iida.next_setting(moon))
moon_age = iida.date - ephem.previous_new_moon(iida.date)
str_moon_age = str(round(moon_age,1))
############################################################
### 暦データをファイルに出力
f=open("koyomi.txt","w", encoding='utf-8')
# 次回 月の出・月の入り等を表示
f.write('次の月の出 ' + next_moonrise.strftime("%m-%d %H:%M") + '\n')
f.write('次の月の入 ' + next_moonset.strftime("%m-%d %H:%M") + '\n')
f.write('現在の月齢:' + str_moon_age + '\n')
## f.close()
# 太陽日の出の表示
sun = ephem.Sun()
sun.compute(iida)
previous_sunrise = ephem.localtime(iida.previous_rising(sun))
previous_sunset = ephem.localtime(iida.previous_setting(sun))
next_sunrise = ephem.localtime(iida.next_rising(sun))
next_sunset = ephem.localtime(iida.next_setting(sun))
# 今日現在時刻
time_now =ephem.localtime(iida.date)
# 今日の日付と次の日の出の日付を比較し、日の出時間を選択
# 今日の日付と次の日の出が同じ日付の場合は、次の日の出・日の入りを使う
if next_sunrise.day == time_now.day:
f.write('今日の日の出 ' + next_sunrise.strftime("%m-%d %H:%M") + '\n')
f.write('今日の日の入 ' + next_sunset.strftime("%m-%d %H:%M") + '\n')
# 今日の日付と前の日の入りの日付が同じ場合は、前の日の出と次の日の入りを使う
if (previous_sunrise.day == time_now.day) and (next_sunset.day == time_now.day) :
f.write('今日の日の出 ' + previous_sunrise.strftime("%m-%d %H:%M") + '\n')
f.write('今日の日の入 ' + next_sunset.strftime("%m-%d %H:%M") + '\n')
# 今日の日付と次の日の入りの同じ場合は、前の日の出と次の日の入りを使う
if (previous_sunrise.day == time_now.day) and (previous_sunset.day == time_now.day) and (time_now.hour < 20):
f.write('今日の日の出 ' + previous_sunrise.strftime("%m-%d %H:%M") + '\n')
f.write('今日の日の入 ' + previous_sunset.strftime("%m-%d %H:%M") + '\n')
# 今日の20時以降は明日の日の出、日の入りを表示
if time_now.hour > 20:
f.write('明日の日の出 ' + next_sunrise.strftime("%m-%d %H:%M") + '\n')
f.write('明日の日の入 ' + next_sunset.strftime("%m-%d %H:%M") + '\n')
f.close()
2、月の満ち欠けの表現 — 球面表示
次に月の満ち欠けの様子を表示するために、Matplotlibで三次元の球面を平面に投射して満ち欠けを表現する。
太陽と月の離角が0度の時が新月、180度の時が満月となる。太陽と月の離角は、moon.elong メソッドで求められ、rad2deg メソッドでラジアンから度の単位に変換し、半球を黄色に塗りつぶした球体をこの度数分だけ回転させると満ち欠けが表現できる。
月の満ち欠けイラスト作成プログラム:
#########################################################
## 月齢イラスト作成
#########################################################
# 月と太陽の離角を計算
moon.compute(iida)
moon_elong = np.rad2deg(moon.elong)
# 描画領域を準備
fig = plt.figure(figsize=(5,5))
ax = fig.gca(projection='3d')
# x, y, z軸の範囲設定
ax.set_xlim([-0.7, 0.7])
ax.set_ylim([-0.7, 0.7])
ax.set_zlim([-0.7, 0.7])
# x, y, z軸や目盛を非表示に
for a in [ax.xaxis, ax.yaxis, ax.zaxis]:
a.set_ticklabels([])
a._axinfo['grid']['linewidth'] = 0
a._axinfo['tick']['linewidth'] = 0
# 背景の x, y, z面を非表示に
for a in [ax.w_xaxis, ax.w_yaxis, ax.w_zaxis]:
a.line.set_linewidth(0)
a.set_pane_color((0., 0., 0., 0.))
# 背面を灰色に
ax.set_facecolor('lightgray')
# メッシュ状の球面 (u, v) を準備し、(x, y, z) 値を計算
u, v = np.mgrid[0:2*np.pi:50j, 0:np.pi:25j] # u:接線方向 v:動経方向
x = np.cos(u) * np.sin(v)
y = np.sin(u) * np.sin(v)
z = np.cos(v)
# メッシュの球面に貼りつける色を準備(半分だけ黄色に)
colors = np.zeros((50, 25, 3))
for i in range(0, 25):
for j in range(0, 25):
colors[i][j][0] = 1
colors[i][j][1] = 1
colors[i][j][2] = 0
# 球面をプロット
ax.plot_surface(x, y, z, facecolors = colors, shade = False)
# グラフを見る方向を設定
ax.view_init(elev = 0, azim = moon_elong - 90)
plt.savefig("moonage.png")
plt.show()
このプログラムから吐き出される暦データと、月齢イメージ画像
koyomi.txt: 次の月の出 02-08 03:46 次の月の入り 02-08 13:37 現在の月齢: 25.1 今日の日の出 02-07 06:43 今日の日の入り 02-07 17:22
画像データ:moonage.png
3、暦データのWordpressへの読み込み
続いて、このファイルデータと画像データをWordPressのトップページに読み込むPHPプログラムを作成。
moonage-write.pyで作成したデータファイルは/opendb/data下に格納されているので、そこからファイルを読み込んでHTMLの記述を出力させる。
// koyomi.php
//テキストファイルを読み込み、各行を配列に格納し、表示する。
$url = "https://****/opendb/data/koyomi.txt";
$fp = file_get_contents($url);
//var_dump($fp);
$ary[] = explode("\n",$fp); /*ファイルを全て配列[][]に入れる*/
//print_r($ary);
//var_dump($ary);
//fontawesomeで太陽、月のマークをデータの前に読み込んで付加
echo ''.$ary[0][3].'';
echo ''.$ary[0][4].'';
echo ''.$ary[0][0].'';
echo ''.$ary[0][1].'';
12行目-15行目のecho以下のコードはWordPressエディターで文字化けが発生しているが、正確には、
echo ''.$ary[0][1].'\n';
4、作成プログラムと保存ディレクトリ
作成・更新プログラム:
・moonage-write.py : 日の出、月の出、月齢計算、月齢イメージ画像作成
・koyomi.php :日の出、日の入、月の出、月の入データを読み込みHTML表示するプログラム
・getsurei.php : 月齢データ、月齢画像を読み込み、HTML表示するプログラム
・koyomi.sh :moonage-write.pyで出力されたファイル:koyomi.txt, moonage.pngを/www/opendb/dataに移動するシェルスクリプト
・job_15min_sakura.sh : このシェルスクリプトにkoyomi.shを走らせるスクリプトを追記
保存ディレクトリ:
/work2/— job_15min_sakura.sh
/work2/koyomi/— moonage-write.php, koyomi.sh
/wp-content/themes/cocoon-child-master/— koyomi.php, getsurei.php
プログラム作成中に出くわした問題
・文字コード(unicode)の扱いについて
さくらインターネットの標準出力コードはASCII。したがって、SSHのコンソールでは日本語が「????」のように表示されてしまう。そこで、Cシェルの環境設定ファイル:.cshrc – csh resource scriptに下記を追加したところ、SSHで日本語が見えるようになった。
setenv LANG ja_JP.UTF-8
この環境でPythonプログラムを走らせると、以下のようなprint関数でも日本語が扱え問題なく機能する。
f=open("koyomi.txt","w")
# 次回 月の出・月の入り等を表示
print('次の月の出 ', next_moonrise.strftime("%m-%d %H:%M"), file=f)
print('次の月の入り ', next_moonset.strftime("%m-%d %H:%M"), file=f)
print('現在の月齢:', str_moon_age, file=f)
f.close()
しかし、それはあくまでもLocalな設定環境なので、CRON設定で動作させようとすると、さくらインターネットのオリジナル環境(ASCII)に戻りエラーとなることが分かった。そこで、暦データをファイルに保存する際は’utf-8’のエンコードを設定することで問題を解決した。
備忘録としてファイル保存のスクリプト例を載せておく。
## write() のサンプル1 ASCIIでも動作する
text = 'Python study'
file = open('test1.txt', 'w')
file.write(text)
file.close()
## write() のサンプル2 日本語バイト文字の場合はencodingしないとエラーとなる
f = open('test2.txt','w', encoding='utf-8')
f.write('Python学習')
f.close()
Pyephemによる暦計算は難なくできたが、さくらインターネットで日本語を取り扱う手続きに手間取った。今回のトラブルで、これまで何となく扱ってきた文字コードについて勉強することができた。(まだ全貌を理解しているとは言い難いが。)
参考 SSHでのサーバー環境の確認
<.cshrcに日本語指定がない場合>
%ll
drwx---r-x 4 miyasan users 512 Jul 15 2017 2017-06-27 WROOM-02 firm_install??????
drwx---r-x 3 miyasan users 512 Jul 4 2017 2017-07-04_sakura_python
drwx---r-x 5 miyasan users 1024 Jul 15 2017 2017-07-14_arduino_refine
drwx---r-x 5 miyasan users 1536 Aug 30 13:36 2018-02-06_?????????
drwx---r-x 2 miyasan users 1536 Feb 14 2020 2020-02-26 ????????????3rd????????????
% locale
LANG=
LC_CTYPE="C"
LC_COLLATE="C"
LC_TIME="C"
LC_NUMERIC="C"
LC_MONETARY="C"
LC_MESSAGES="C"
LC_ALL=
% python3
Python 3.6.5 (default, Apr 9 2020, 22:09:43)
>>> import sys
>>> sys.getdefaultencoding()
'utf-8'
>>> sys.stdout.encoding
'US-ASCII'
>>>
<.cshrcに日本語指定を記述した場合>
setenv LANG ja_JP.UTF-8
% python3
Python 3.6.5 (default, Apr 9 2020, 22:09:43)
>>> import sys
>>> sys.getdefaultencoding()
'utf-8'
>>> sys.stdout.encoding
'UTF-8'
>>>
% locale
LANG=ja_JP.UTF-8
LC_CTYPE="ja_JP.UTF-8"
LC_COLLATE="ja_JP.UTF-8"
LC_TIME="ja_JP.UTF-8"
LC_NUMERIC="ja_JP.UTF-8"
LC_MONETARY="ja_JP.UTF-8"
LC_MESSAGES="ja_JP.UTF-8"
LC_ALL=
======================================
FreeBSD ハンドブック(16.2. 地域化の利用 抜粋)
======================================
地域化の設定は、言語コード、 国コード、エンコーディングという三つの要素を基本とします。
現在のロケールの設定を調べるには、 以下のコマンドを実行してください。
% locale
いくつかの言語 (例えば中国語や日本語) は、 ASCII 文字では表すことができないので、 ワイド文字や多バイト文字を用いた拡張された言語のエンコードが必要となります。ロケールの設定は、ユーザの ~/.login_conf、 またはユーザのシェルの初期設定ファイルである ~/.profile, ~/.bashrc または ~/.cshrc で行います。
以下の二つの環境変数を設定する必要があります。
LANG: ロケールを設定します。
MM_CHARSET: アプリケーションで使用される MIME 文字セットを指定します。
コメント