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.3.2 2023/02/11 getSolarTerm72() 表記改訂:水澤腹堅→水沢腹堅
4.3.1 2023/02/03 表記改訂:バクムーン→バックムーン,スタージャンムーン→スタージョンムーン,七十二候
4.3.0 2023/01/14 コメント表記などを見直した,tenshanichi()追加
4.2.0 2023/01/11 getTimeDifference(),setTimeDifference()追加
4.1.0 2023/01/09 太陽,月の位置計算の基準をUTCに変更した
pahooInputData.php 更新履歴
バージョン 更新日 内容
1.5.0 2024/01/28 exitIfExceedVersion() 追加
1.4.2 2024/01/28 exitIfLessVersion() メッセージ修正
1.4.1 2023/09/30 コメントの訂正
1.4.0 2023/09/09 $_GET, $_POST参照をfilter_input()関数に置換
1.3.0 2023/07/11 roundFloat() 追加

準備:初期値など

  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が必ずしも満月の日になるとは限らない。

1807: /**
1808:  * 指定した日時の次の満月の日時を求める(日時はローカル時間).
1809:  * 太陽と月の黄緯差180度になる日時を近似計算で求める.
1810:  * 参考サイト https://www.pahoo.org/e-soul/webtech/php02/php02-50-01.shtm
1811:  * @param   int $year, $month, $day  年月日(グレゴリオ暦)
1812:  * @param   float $hour, $min, $sec 時分秒(ローカル時間)
1813:  * @param   float $tdiff UTCとの時差;NULLの時はTDIFF参照
1814:  * @return  array($year, $month, $day, $hour, $min, $sec)  日時(ローカル時間)
1815: */
1816: function next_fullmoon($year, $month, $day, $hour, $min, $sec, $tdiff=NULL) {
1817:     $dif = 29.55;       //一朔望月(近似計算の指標)
1818:     $jd  = $this->Gregorian2JD($year, $month, $day, $hour, $min, $sec, $tdiff);
1819:     $jdc = (float)$jd;
1820: 
1821:     //ニュートン法による近似計算
1822:     for ($i = 1$i < 25$i++) {
1823:         list($year, $month, $day, $hour, $min, $sec) = $this->JD2Gregorian($jdc, $tdiff);
1824:         $ym = $this->longitude_moon($year, $month, $day, $hour, $min, $sec, $tdiff, $tdiff);
1825:         $ys = $this->longitude_sun($year, $month, $day, $hour, $min, $sec, $tdiff, $tdiff);
1826: 
1827:         //太陽と月の黄緯差180度を目指す.
1828:         $dy = $ym - $ys - 180.0;
1829: 
1830:         //引き込み範囲に入ったら補正する.
1831:         if ($dy <-360.0) {
1832:             $dy +360.0;
1833:         } else if ($dy >0.0) {
1834:             $dy -360.0;
1835:         }
1836: 
1837:         //誤差内に入ったらループを脱出する.
1838:         if ($dy >0.0 && $dy <0.0001break;
1839:         if ($dy < -0.0 && $dy >-0.0001)    break;
1840: 
1841:         $dd = $dy / $dif;
1842:         $jdc -$dd;
1843:     }
1844: 
1845:     return array($year, $month, $day, $hour, $min, $sec);
1846: }

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

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

1620: /**
1621:  * J2000.0からの経過年数における月の視差(度)を求める。
1622:  * 参考サイト https://www.pahoo.org/e-soul/webtech/php02/php02-44-01.shtm
1623:  * @param   float $jy 2000.0からの経過年数
1624:  * @return  float 月の視差(度)
1625: */
1626: function __dif_moon($jy) {
1627:     $p_moon  =  0.0003 * sin(deg2rad($this->__angle(227.0  +  4412.0   * $jy)));
1628:     $p_moon +=  0.0004 * sin(deg2rad($this->__angle(194.0  +  3773.4   * $jy)));
1629:     $p_moon +=  0.0005 * sin(deg2rad($this->__angle(329.0  +  8545.4   * $jy)));
1630:     $p_moon +=  0.0009 * sin(deg2rad($this->__angle(100.0  + 13677.3   * $jy)));
1631:     $p_moon +=  0.0028 * sin(deg2rad($this->__angle(  0.0  +  9543.98  * $jy)));
1632:     $p_moon +=  0.0078 * sin(deg2rad($this->__angle(325.7  +  8905.34  * $jy)));
1633:     $p_moon +=  0.0095 * sin(deg2rad($this->__angle(190.7  +  4133.35  * $jy)));
1634:     $p_moon +=  0.0518 * sin(deg2rad($this->__angle(224.98 +  4771.989 * $jy)));
1635:     $p_moon +=  0.9507 * sin(deg2rad($this->__angle(90.0)));
1636: 
1637:     return $p_moon;
1638: }

1640: /**
1641:  * 指定した日時における月の視差(度)を求める(日時はローカル時間)。
1642:  * 参考サイト https://www.pahoo.org/e-soul/webtech/php02/php02-44-01.shtm
1643:  * @param   int $year, $month, $day  グレゴリオ暦による年月日
1644:  * @param   float $hour, $min, $sec 時分秒(ローカル時間)
1645:  * @param   float $tdiff UTCとの時差;NULLの時はTDIFF
1646:  * @return  float 月の視差
1647: */
1648: function dif_moon($year, $month, $day, $hour, $min, $sec, $tdiff=NULL) {
1649:     $jy = $this->Gregorian2JY($year, $month, $day, $hour, $min, $sec, $tdiff);
1650: 
1651:     return $this->__dif_moon($jy);
1652: }

1654: /**
1655:  * 指定した日時における月の視半径(度)を求める(日時はローカル時間)。
1656:  * 参考サイト https://www.pahoo.org/e-soul/webtech/php02/php02-50-01.shtm
1657:  * @param   int $year, $month, $day  グレゴリオ暦による年月日
1658:  * @param   float $hour, $min, $sec 時分秒(ローカル時間)
1659:  * @param   float $tdiff UTCとの時差;NULLの時はTDIFF
1660:  * @return  float 月の視半径(度)
1661: */
1662: function rad_moon($year, $month, $day, $hour, $min, $sec, $tdiff=NULL) {
1663:     $dif = $this->dif_moon($year, $month, $day, $hour, $min, $sec, $tdiff);
1664:     $rad = asin(0.2725 * sin(deg2rad($dif)));
1665: 
1666:     return rad2deg($rad);
1667: }

月の視半径 rad は、下記の計算式により月の視差から求めることができる。
これを rad_moon として実装している。
 mimetex 

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

 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 に格納する。

解説:満月の呼び名

1888: /**
1889:  * 指定した月の満月の呼び名を求める.
1890:  * 参考サイト https://www.pahoo.org/e-soul/webtech/php02/php02-50-01.shtm
1891:  * @param   int $month  月
1892:  * @return  string 満月の呼び名
1893: */
1894: function getFullMoonNickname($month) {
1895:     $table_jp = array(
1896: 'ウルフムーン',
1897: 'スノームーン',
1898: 'ワームムーン',
1899: 'ピンクムーン',
1900: 'フラワームーン',
1901: 'ストロベリームーン',
1902: 'バックムーン',         //または「バクムーン」.発音はbʌkだが,buckskinをバックスキンと読むことから,ここでは「バクムーン」にした.
1903: 'スタージョンムーン',   //または「スタージャンムーン」.Twitterでアンケートをとて「スタージョンムーン」にした. https://twitter.com/papa_pahoo/status/1620259072871395328
1904: 'ハーベストムーン',
1905: 'ハンターズムーン',
1906: 'ビーバームーン',
1907: 'コールドムーン'
1908: );
1909: 
1910:     $table_en = array(
1911: 'Wolf Moon',
1912: 'Snow Moon',
1913: 'Worm Moon',
1914: 'Pink Moon',
1915: 'Flower Moon',
1916: 'Strawberry Moon',
1917: 'Buck Moon',
1918: 'Sturgeon Moon',
1919: 'Harvest Moon',
1920: 'Hunter\#x27;s Moon',
1921: 'Beaver Moon',
1922: 'Cold Moon'
1923: );
1924: 
1925:     $month = (int)$month;
1926:     if (($month >1&& ($month << 12)) {
1927:         if ($this->language == 'jp') {
1928:             $name = $table_jp[$month - 1];
1929:         } else {
1930:             $name = $table_en[$month - 1];
1931:         }
1932:     } else {
1933:         $name = '';
1934:     }
1935: 
1936:     return $name;
1937: }

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

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

解説:一覧表作成

 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