PHPでスーパームーンを計算する

(1/1)
2018年(平成30年)1月2日の満月は、月が地球に最も近づき大きく見えることから「スーパームーン」と呼ばれる。
PHPで月齢を計算」で月齢を求めるプログラムを作ったが、今回は満月の時の月の大きさ(視半径)を計算するプログラムを作ってみることにする。

(2023年2月3日)表記改訂:バクムーン→バックムーン,スタージャンムーン→スタージョンムーン
(2023年1月14日)数値入力にSpinner、pahooInputDataを導入した。
(2022年12月26日)満月の日を取得するアルゴリズムを見直した。

目次

サンプル・プログラムの実行例

PHPでスーパームーンを計算する
月の平均視半径は 15'32"58(1'は1度の60分の1、1"はさらに60分の1)であるから、2018年(平成30年)1月2日のスーパームーンは平均より半径が7.7%も大きく見えることになる。

サンプル・プログラム

圧縮ファイルの内容
supermoon.phpサンプル・プログラム本体。
pahooCalendar.php暦・潮位計算クラス pahooCalendar。
暦・潮位計算クラスの使い方は「PHPで二十四節気・七十二候一覧を作成」「PHPで月齢を計算」「PHPで日出没・月出没・月齢・潮を計算」「PHPで潮位を計算する」などを参照。include_path が通ったディレクトリに配置すること。
pahooInputData.phpデータ入力に関わる関数群。
使い方は「数値入力とバリデーション」「文字入力とバリデーション」などを参照。include_path が通ったディレクトリに配置すること。
supermoon.php 更新履歴
バージョン 更新日 内容
1.5.0 2023/01/14 数値入力にSpinner、pahooInputData導入
1.4.0 2023/01/09 pahooCalendarクラスの計算精度を向上
1.3.0 2021/01/04 満月の日を取得するアルゴリズムを見直した
1.2 2021/01/04 PHP8対応,満月の呼び名等を追加
1.1 2018/01/03 明るさの比などを一覧表示
pahooCalendar.php 更新履歴
バージョン 更新日 内容
4.5.0 2024/03/17 ヒジュラ暦メソッドを追加
4.4.1 2024/03/17 getCabinetOfficeHolidayTable() -- bug-fix
4.4.0 2024/02/25 内閣府の祝日表を参照できるようにした
4.3.2 2023/02/11 getSolarTerm72() 表記改訂:水澤腹堅→水沢腹堅
4.3.1 2023/02/03 表記改訂:バクムーン→バックムーン,スタージャンムーン→スタージョンムーン,七十二候
pahooInputData.php 更新履歴
バージョン 更新日 内容
1.8.0 2024/11/12 validRegexPattern() 追加
1.7.0 2024/10/09 validURL() validEmail() 追加
1.6.0 2024/10/07 isButton() -- buttonタグに対応
1.5.0 2024/01/28 exitIfExceedVersion() 追加
1.4.2 2024/01/28 exitIfLessVersion() メッセージ修正

準備:初期値など

supermoon.php

  36: //指定できる西暦年の範囲
  37: define('MIN_YEAR', 1901);
  38: define('MAX_YEAR', 2099);
  39: 
  40: //表示言語(jp:日本語, en:英語, en3:英語略記)
  41: define('LANGUAGE', 'jp');
  42: 
  43: //世界時からの時差(日本標準時)
  44: define('UTCDIFF', +9.0);
  45: 
  46: //計算期間(月)
  47: define('CALC_TERM', 12);
  48: 
  49: //視半径が最小/最大の時の背景色
  50: define('MIN_COLOR', '88EEFF');
  51: define('MAX_COLOR', 'FFEE88');
  52: 
  53: //Spinner - jQuery UI を使用するかどうか
  54: define('USESPINNER', TRUE);
  55: 
  56: //数値増減クリックで即判定するかどうか(TRUE:即判定,FALE:判定ボタンを用意)
  57: define('ONCHANGE', FALSE);
  58: 
  59: //表示幅(単位:ピクセル)
  60: define('WIDTH', 600);
  61: 
  62: //require_once()で呼ぶファイルはinclude_pathが通っているフォルダに配置すること。
  63: //暦計算クラス
  64: require_once('pahooCalendar.php');
  65: 
  66: //データ入力に関わる関数群
  67: require_once('pahooInputData.php');

あらかじめ、計算期間(月)と世界時からの時差を定数として定義しておく。

月齢、月の視半径、各種カレンダー計算は、ユーザークラス "pahooCalendar" に用意したメソッド群を利用する。
オブジェクト生成時に、表示言語として "ja"(日本語)を、世界時との時差としてユーザー定義の定数 UTCDIFF を渡しておく。こうすることで、pahooCalendar クラスが出力する文字列は日本語に、日時計算は時差 UTCDIFF がある前提で計算を行う。

数値入力に jQuery UISpinner を使用している。設定でOFFにすることもできる。Spinner の使い方については、「Spinner - jQuery UI - PHPで素数かどうか判定」をご覧いただきたい。

入力した数値の取得とバリデーションチェックに、ユーザー定義クラス群 "pahooInputData.php" を利用している。

準備:次の満月の日時を求める

月と太陽の位置関係
月と太陽の位置関係
満月は、月と太陽の黄経差が180度になる瞬間である。
一方、月齢は朔の日(新月の日)を第1日とする日数であるから、月齢15が必ずしも満月の日になるとは限らない。

pahooCalendar.php

1874: /**
1875:  * 指定した日時の次の満月の日時を求める(日時はローカル時間).
1876:  * 太陽と月の黄緯差180度になる日時を近似計算で求める.
1877:  * 参考サイト https://www.pahoo.org/e-soul/webtech/php02/php02-50-01.shtm
1878:  * @param   int $year, $month, $day  年月日(グレゴリオ暦)
1879:  * @param   float $hour, $min, $sec 時分秒(ローカル時間)
1880:  * @param   float $tdiff UTCとの時差;NULLの時はTDIFF参照
1881:  * @return  array($year, $month, $day, $hour, $min, $sec)  日時(ローカル時間)
1882: */
1883: function next_fullmoon($year, $month, $day, $hour, $min, $sec, $tdiff=NULL) {
1884:     $dif = 29.55;       //一朔望月(近似計算の指標)
1885:     $jd  = $this->Gregorian2JD($year, $month, $day, $hour, $min, $sec, $tdiff);
1886:     $jdc = (float)$jd;
1887: 
1888:     //ニュートン法による近似計算
1889:     for ($i = 1$i < 25$i++) {
1890:         list($year, $month, $day, $hour, $min, $sec) = $this->JD2Gregorian($jdc, $tdiff);
1891:         $ym = $this->longitude_moon($year, $month, $day, $hour, $min, $sec, $tdiff, $tdiff);
1892:         $ys = $this->longitude_sun($year, $month, $day, $hour, $min, $sec, $tdiff, $tdiff);
1893: 
1894:         //太陽と月の黄緯差180度を目指す.
1895:         $dy = $ym - $ys - 180.0;
1896: 
1897:         //引き込み範囲に入ったら補正する.
1898:         if ($dy <-360.0) {
1899:             $dy +360.0;
1900:         } else if ($dy >0.0) {
1901:             $dy -360.0;
1902:         }
1903: 
1904:         //誤差内に入ったらループを脱出する.
1905:         if ($dy >0.0 && $dy <0.0001break;
1906:         if ($dy < -0.0 && $dy >-0.0001)    break;
1907: 
1908:         $dd = $dy / $dif;
1909:         $jdc -$dd;
1910:     }
1911: 
1912:     return array($year, $month, $day, $hour, $min, $sec);
1913: }

そこで、指定した日時から次に、月と太陽の黄経差が180度になる日時を求めるメソッド next_fullmoon を用意した。
太陽の視黄経の計算メソッド longitude_sun と月の視黄経の計算メソッド longitude_moon はすでに説明したとおりだが、黄経値から年月日を計算する逆関数は存在しないため、近似計算によって求めている。このため、国立天文台が発表する時刻と数分のズレがある。

解説:月の視半径を求める

pahooCalendar.php

1687: /**
1688:  * J2000.0からの経過年数における月の視差(度)を求める。
1689:  * 参考サイト https://www.pahoo.org/e-soul/webtech/php02/php02-44-01.shtm
1690:  * @param   float $jy 2000.0からの経過年数
1691:  * @return  float 月の視差(度)
1692: */
1693: function __dif_moon($jy) {
1694:     $p_moon  =  0.0003 * sin(deg2rad($this->__angle(227.0  +  4412.0   * $jy)));
1695:     $p_moon +=  0.0004 * sin(deg2rad($this->__angle(194.0  +  3773.4   * $jy)));
1696:     $p_moon +=  0.0005 * sin(deg2rad($this->__angle(329.0  +  8545.4   * $jy)));
1697:     $p_moon +=  0.0009 * sin(deg2rad($this->__angle(100.0  + 13677.3   * $jy)));
1698:     $p_moon +=  0.0028 * sin(deg2rad($this->__angle(  0.0  +  9543.98  * $jy)));
1699:     $p_moon +=  0.0078 * sin(deg2rad($this->__angle(325.7  +  8905.34  * $jy)));
1700:     $p_moon +=  0.0095 * sin(deg2rad($this->__angle(190.7  +  4133.35  * $jy)));
1701:     $p_moon +=  0.0518 * sin(deg2rad($this->__angle(224.98 +  4771.989 * $jy)));
1702:     $p_moon +=  0.9507 * sin(deg2rad($this->__angle(90.0)));
1703: 
1704:     return $p_moon;
1705: }

pahooCalendar.php

1707: /**
1708:  * 指定した日時における月の視差(度)を求める(日時はローカル時間)。
1709:  * 参考サイト https://www.pahoo.org/e-soul/webtech/php02/php02-44-01.shtm
1710:  * @param   int $year, $month, $day  グレゴリオ暦による年月日
1711:  * @param   float $hour, $min, $sec 時分秒(ローカル時間)
1712:  * @param   float $tdiff UTCとの時差;NULLの時はTDIFF
1713:  * @return  float 月の視差
1714: */
1715: function dif_moon($year, $month, $day, $hour, $min, $sec, $tdiff=NULL) {
1716:     $jy = $this->Gregorian2JY($year, $month, $day, $hour, $min, $sec, $tdiff);
1717: 
1718:     return $this->__dif_moon($jy);
1719: }

pahooCalendar.php

1721: /**
1722:  * 指定した日時における月の視半径(度)を求める(日時はローカル時間)。
1723:  * 参考サイト https://www.pahoo.org/e-soul/webtech/php02/php02-50-01.shtm
1724:  * @param   int $year, $month, $day  グレゴリオ暦による年月日
1725:  * @param   float $hour, $min, $sec 時分秒(ローカル時間)
1726:  * @param   float $tdiff UTCとの時差;NULLの時はTDIFF
1727:  * @return  float 月の視半径(度)
1728: */
1729: function rad_moon($year, $month, $day, $hour, $min, $sec, $tdiff=NULL) {
1730:     $dif = $this->dif_moon($year, $month, $day, $hour, $min, $sec, $tdiff);
1731:     $rad = asin(0.2725 * sin(deg2rad($dif)));
1732: 
1733:     return rad2deg($rad);
1734: }

月の視半径 rad は、下記の計算式により月の視差から求めることができる。
これを rad_moon として実装している。
\[ rad = sin^{-1}(0.2725 \times sin(dif)) \]

解説:ある期間に起きる満月と視半径を計算

supermoon.php

 201: /**
 202:  * ある期間に起きる満月と視半径を計算
 203:  * @param   int $year  開始年(西暦)
 204:  * @param   int $month 開始月
 205:  * @param   int $term  計算期間(月)
 206:  * @param   array $items 計算結果を格納する配列
 207:  * @return  int 満月の回数
 208: */
 209: function calcSuperMoon($year, $month, $term, &$items) {
 210:     //暦計算に関わるクラス
 211:     $pcl = new pahooCalendar(LANGUAGE, UTCDIFF);
 212: 
 213:     $i = $cnt = 0;
 214:     $day = 1;
 215:     while ($i < $term) {
 216:         //次の満月の日時を求める
 217:         list($year1, $month1, $day1, $hour1, $min1, $sec1) =$pcl->next_fullmoon($year, $month, $day, 0, 0, 0);
 218:         $items[$cnt]['year']  = $year1;
 219:         $items[$cnt]['month'] = $month1;
 220:         $items[$cnt]['day']   = $day1;
 221:         $items[$cnt]['week']  = $pcl->getWeekString($year1, $month1, $day1);
 222:         $items[$cnt]['hour']  = $hour1;
 223:         $items[$cnt]['min']   = $min1 + round($sec1 / 60.0, 0);
 224:         //月の視半径
 225:         $items[$cnt]['rad'] = $pcl->rad_moon($year1, $month1, $day1, $hour1, $min1, $sec1);
 226:         //満月の呼び名
 227:         $items[$cnt]['name']  = $pcl->getFullMoonNickname($month1);
 228:         //次のパラメータを用意する
 229:         $cnt++;
 230:         $year = $year1;
 231:         if ($month < $month1)    $i++;
 232:         $month = $month1;
 233:         $day = $day1 + 20.0;    //次の計算基準は26日後
 234:         if ($day > $pcl->getDaysInMonth($year, $month)) {
 235:             $day = 1;
 236:             $month++;
 237:             $i++;
 238:             if ($month > 12) {
 239:                 $month = 1;
 240:                 $year++;
 241:             }
 242:         }
 243:     }
 244:     //オブジェクト解放
 245:     $pcl = NULL;
 246: 
 247:     return $cnt;
 248: }

ユーザー関数 calcSuperMoon は、指定した期間の間に起きる満月と視半径を計算し、配列 $items に格納する。

解説:満月の呼び名

pahooCalendar.php

1955: /**
1956:  * 指定した月の満月の呼び名を求める.
1957:  * 参考サイト https://www.pahoo.org/e-soul/webtech/php02/php02-50-01.shtm
1958:  * @param   int $month  月
1959:  * @return  string 満月の呼び名
1960: */
1961: function getFullMoonNickname($month) {
1962:     $table_jp = array(
1963: 'ウルフムーン',
1964: 'スノームーン',
1965: 'ワームムーン',
1966: 'ピンクムーン',
1967: 'フラワームーン',
1968: 'ストロベリームーン',
1969: 'バックムーン',         //または「バクムーン」.発音はbʌkだが,buckskinをバックスキンと読むことから,ここでは「バクムーン」にした.
1970: 'スタージョンムーン',   //または「スタージャンムーン」.Twitterでアンケートをとて「スタージョンムーン」にした. https://twitter.com/papa_pahoo/status/1620259072871395328
1971: 'ハーベストムーン',
1972: 'ハンターズムーン',
1973: 'ビーバームーン',
1974: 'コールドムーン'
1975: );
1976: 
1977:     $table_en = array(
1978: 'Wolf Moon',
1979: 'Snow Moon',
1980: 'Worm Moon',
1981: 'Pink Moon',
1982: 'Flower Moon',
1983: 'Strawberry Moon',
1984: 'Buck Moon',
1985: 'Sturgeon Moon',
1986: 'Harvest Moon',
1987: 'Hunter\#x27;s Moon',
1988: 'Beaver Moon',
1989: 'Cold Moon'
1990: );
1991: 
1992:     $month = (int)$month;
1993:     if (($month >1&& ($month << 12)) {
1994:         if ($this->language == 'jp') {
1995:             $name = $table_jp[$month - 1];
1996:         } else {
1997:             $name = $table_en[$month - 1];
1998:         }
1999:     } else {
2000:         $name = '';
2001:     }
2002: 
2003:     return $name;
2004: }

メソッド getFullMoonNickname を呼び出すと、指定したつきの満月の呼び名を日本語または英語で返す。

7月の Buck Moonbuk(雄ジカ)は発音はbʌkだが、buckskinをバックスキンと読むことから、ここでは「バクムーン」にした。
8月の Sturgeon Moonsturgeon(チョウザメ)の発音はstɚːdʒənだが、Wikipedia日本語版にある Sturgeon をもつ人名の表記は揺れている。
  • ウィリアム・スタージャン - イギリスの物理学者。
  • シオドア・スタージョン - アメリカのSF作家。
  • ニコラ・スタージョン - スコットランドの政治家。
  • ローリン・S・スタージョン - アメリカの映画監督
また、日本気象協会はスタージャンムーンを使っている。そこでTwitterでアンケートをとり、ここでは「スタージョンムーン」にした。

解説:一覧表作成

supermoon.php

 251: /**
 252:  * 満月一覧から表示用HTMLを求める.
 253:  * @param   array $items 計算結果
 254:  * @return  string HTML文字列
 255: */
 256: function makeTable($items) {
 257:     //最大・最小の視半径を求める
 258:     $minrad = 999;
 259:     $minid = -1;
 260:     $maxrad = 0;
 261:     $maxid = -1;
 262:     foreach ($items as $key=>$item) {
 263:         if ($item['rad'< $minrad) {
 264:             $minrad = $item['rad'];
 265:             $minid = $key;
 266:         }
 267:         if ($item['rad'> $maxrad) {
 268:             $maxrad = $item['rad'];
 269:             $maxid = $key;
 270:         }
 271:     }
 272: 
 273:     //一覧表作成
 274:     $width = WIDTH;
 275:     $outstr =<<< EOT
 276: <table style="width:{$width};">
 277: <tr><th>年月日・時</th><th colspan="2">月の視半径<br /><span class="comp">(最小半径を100%)</span></th><th>明るさの比<br /><span class="comp">(最小半径を100%)</span></th><th>呼び名</th></tr>
 278: 
 279: EOT;
 280:     foreach ($items as $key=>$item) {
 281:         $ymd = sprintf("%04d年%02d月%02d日(%s) %02d:%02d",
 282:             $item['year'], $item['month'], $item['day'], $item['week'], $item['hour'], $item['min']);
 283:         $mm = intval($item['rad'* 60);
 284:         $s1 = intval(($item['rad'* 60 - $mm* 60);
 285:         $s2 = ($item['rad'* 60 - $mm - $s1 / 60* 60 * 100;
 286:         $rad = sprintf("%2d'%02d\".%02d", $mm, $s1, $s2);
 287:         $rat = sprintf("%3.1f", $item['rad'] / $minrad * 100);
 288:         $brg = sprintf("%3.1f", pow($item['rad'] / $minrad, 2* 100);
 289: 
 290:         //背景色
 291:         if ($key == $minid) {
 292:             $color = ' style="background-color:#' . MIN_COLOR . ';"';
 293:         } else if ($key == $maxid) {
 294:             $color = ' style="background-color:#' . MAX_COLOR . ';"';
 295:         } else {
 296:             $color = '';
 297:         }
 298: 
 299:         $outstr .=<<< EOT
 300: <tr{$color}>
 301: <td>{$ymd}</td>
 302: <td>{$rad}</td>
 303: <td>{$rat}%</td>
 304: <td>{$brg}%</td>
 305: <td>{$item['name']}</td>
 306: </tr>
 307: 
 308: EOT;
 309:     }
 310:     $outstr .=<<< EOT
 311: </table>
 312: </body>
 313: 
 314: EOT;
 315: 
 316:     return $outstr;
 317: }

ユーザー関数 makeTable は、calcSuperMoon の計算結果を格納した配列 $items を縦覧し、HTMLのテーブルを作成してゆく。

冒頭で、期間中の最小半径を求める。
これを基準に、視半径の比率、明るさの比率(半径の2乗)を計算し、一覧表にしてゆく。

活用例

みんなの知識 ちょっと便利帳」では、このサンプル・プログラムを活用し、「スーパームーンを調べる」というサービスを提供している。ご活用をありがとうございます。

参考サイト

(この項おわり)
header