PHPで潮位を計算する

(1/1)
釣り人でなくても、潮干狩り鳴門の渦潮観光など、潮見表を見ること少なくない。台風による浸水被害を予測するときにも必要だ。
さて、「PHP で日出没・月出没・月齢・潮を計算」では潮の計算を行うプログラムを紹介したが、潮位を計算するには、はるかに複雑な計算を行う必要がある。幸い、コンピュータの計算能力が飛躍的に向上したおかげで、潮位の計算もストレスなく行えるようになった。

そこで、これまで作ってきた "pahooCalendar.php" に、あらた潮位計算クラス "pahooTide" を追加し、Google マップに表示されている地点の近くの海岸において、指定日から 1週間の毎日の満潮・干潮の時刻と潮位を一覧およびグラフ表示するサンプル・プログラムを作ってみることにする。

(2019 年 6 月 24 日)地理院地図、OpenStreetMap も利用できるようにした。緯度・経度から住所を検索する逆ジオコーディングサービスの選択肢を増やした。地図検索,リセット・ボタンを追加した。
(2019 年 4 月 20 日)Google マップと Yahoo!マップを切り替えて使えるようにした。

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

PHPで潮位を計算する
Google マップ表示
PHPで潮位を計算する

目次

サンプル・プログラムのダウンロード

圧縮ファイルの内容
tide.phpサンプル・プログラム本体。
pahooCalendar.php暦計算クラス pahooCalendar、潮位計算クラス pahooTide。
include_pathが通ったディレクトリに配置すること。
tideData.zip計算パラメータ。pahooTideが解凍せずに参照する圧縮ファイル。
pahooGeoCode.php住所・緯度・経度に関わるクラス pahooGeoCode。
使い方は「PHPで住所・ランドマークから最寄り駅を求める」「PHPで住所・ランドマークから緯度・経度を求める」などを参照。include_path が通ったディレクトリに配置すること。
moon/moon_00.png~moon_30.png月齢画像。
makeTideDataFiles.phptideData.zip を自動生成する。
サンプル・プログラム実行時には不要。
table_C1C2.xlsx表-C.1, C.2の元データ。
サンプル・プログラム実行時には不要。
dispC1C2table.php表-C.1, C.2をTABLE表示する。
サンプル・プログラム実行時には不要。

準備:外部クラスなど

潮位、月齢、各種カレンダー計算は、ユーザークラス "pahooCalendar" および "pahooTide" を利用する。クラスファイル "pahooCalendar.php" を nclude_path が通ったディレクトリに配置すること。
Google マップの表示や住所検索には、ユーザークラス "pahooGeoCode" を利用する。クラスファイル "pahooGeoCode.php" を include_path が通ったディレクトリに配置すること。
また、ユーザークラス "pahooTide" が参照するデータファイル "tideData.zip" はクラスファイルと同じディレクトリに配置すること。

準備:pahooGeoCode クラス

0035: class pahooGeoCode {
0036:     var $items;      //検索結果格納用
0037:     var $error;      //エラーフラグ
0038:     var $hits;       //検索ヒット件数
0039:     var $webapi; //直前に呼び出したWebAPI URL
0040: 
0041:     //Google Cloud Platform APIキー
0042:     //https://cloud.google.com/maps-platform/
0043:     //※Google Maps APIを利用しないのなら登録不要
0044:     var $GOOGLE_API_KEY_1 = '**************************';   //HTTPリファラ用
0045:     var $GOOGLE_API_KEY_2 = '**************************';   //IP制限用
0046: 
0047:     //Yahoo! JAPAN Webサービス アプリケーションID
0048:     //https://e.developer.yahoo.co.jp/register
0049:     //※Yahoo! JAPAN Webサービスを利用しないのなら登録不要
0050:     var $YAHOO_APPLICATION_ID = '*****************************';

地図サービスを利用するために、クラスファイル "pahooGeoCode.php" を使用する。組み込み関数  require_once  を使って読めるディレクトリに配置する。ディレクトリは、設定ファイル php.ini に記述されているオプション設定 include_path に設定しておく。
クラスについては「PHP でクラスを使ってテキストの読みやすさを調べる」を参照されたい。

地図として Google マップを利用するのであれば、Google Cloud Platform API キー が必要で、その入手方法は「Google Cloud Platform - WebAPI の登録方法」を、YOLP マップを利用するのであれば、Yahoo! JAPAN Web サービス アプリケーション IDが必要で、その入手方法は「Google Cloud Platform - WebAPI の登録方法」を、それぞれ参照されたい。

準備:地図サービス(WebAPI)の選択

0046: //地図描画サービスの選択
0047: //    0:Google
0048: //    1:Yahoo!JAPAN
0049: //    2:地理院地図・OSM
0050: define('MAPSERVICE', 0);
0051: 
0052: //住所検索サービスの選択
0053: //    0:Google
0054: //    1:Yahoo!JAPAN
0055: //   11:HeartRails Geo API
0056: define('GEOSERVICE', 0);
0057: 
0058: //逆ジオコーディングサービスの選択
0059: //    0:Google
0060: //    1:Yahoo!JAPAN
0061: //   11:HeartRails Geo API
0062: //   21:簡易ジオコーディングサービス
0063: define('REVGEOSERVICE', 21);

表示する地図は、Google マップと Yahoo!マップ(YOLP 地図)から選べる。
あらかじめ、定数 GEOSERVICE に値を設定すること。
PHPで潮位を計算する
Yahoo!マップ表示

全体の流れ

ここでは、『港湾空港技術研究所資料 No.1168』(2007 年 12 月,独立行政法人 港湾空港技術研究所)の「付録C 潮汐の推算」を参考に、プログラムを作成していく。プログラムの動作確認にあたっては、気象庁の潮位表、および TIDE for WINを利用した。後者は Visual Basic のソースコードが公開されており、開発の参考になるだろう。

潮汐は複数の分潮の合成であり、下記の式で求めることができる。
 mimetex   ‥‥(C.1)
η潮位
Z0観測値の平均水面
fi分潮iの振幅に対する補正係数
Hi観測地において計算された振幅
Vi時間とともに変化する分潮引数
ui時間とともに変化する位相の補正係数
ti観測地において計算された位相遅れ
 mimetex 、 mimetex 、 mimetex は観測地に固有の値であり、後述するが、気象庁が公開している各観測地点における分潮一覧表から入手することができる。
ここからしばらくは、クラスファイル "pahooCalendar.php" にある潮位計算クラス "pahooTide" のコードと見比べながら計算原理を説明してゆく。

1615: // 潮位計算 ===============================================================
1616: // @参考URL https://www.pahoo.org/e-soul/webtech/php02/php02-51-01.shtm
1617: class pahooTide {
1618:     const FILE_ZIPNAME = 'tideData.zip';    //計算用パラメータ格納ZIPファイル
1619:     const FILE_C1      = '_C1.txt';        //表-C.1
1620:     const FILE_C2      = '_C2.txt';        //表-C.2
1621:     const FILE_INDEX   = '_index.txt';        //地点一覧
1622:     const FILE_EXT     = '.txt';            //拡張子
1623:     const JST          = -9;              //日本時の時差
1624:     const COMMENT = '#';            //コメント文字
1625:     var $error$errmsg;         //エラーフラグ,エラーメッセージ
1626:     var $_s$_h$_p$_N;      //天文引数
1627: 
1628:     var $c1$c2;                    //表-C.1, C.2
1629:     var $index;                  //地点一覧
1630:     var $port;                       //ある地点の分潮一覧表
1631: 
1632: //分潮記号
1633: var $keys = array('Sa', 'Ssa', 'Mm', 'MSf', 'Mf', '2Q1', 'σ1', 'Q1', 'ρ1', 'O1', 'MP1', 'M1', 'χ1', 'π1', 'P1', 'S1', 'K1', 'ψ1', 'φ1', 'θ1', 'J1', 'SO1', 'OO1', 'OQ2', 'MNS2', '2N2', 'μ2', 'N2', 'ν2', 'OP2', 'M2', 'MKS2', 'λ2', 'L2', 'T2', 'S2', 'R2', 'K2', 'MSN2', 'KJ2', '2SM2', 'MO3', 'M3', 'SO3', 'MK3', 'SK3', 'MN4', 'M4', 'SN4', 'MS4', 'MK4', 'S4', 'SK4', '2MN6', 'M6', 'MSN6', '2MS6', '2MK6', '2SM6', 'MSK6');
1634: 
1635: /**
1636:  * コンストラクタ
1637:  * @param なし
1638:  * @return なし
1639: */
1640: function __construct() {
1641:     $this->error  = FALSE;
1642:     $this->errmsg = '';
1643: 
1644:     //表-C.1, C.2, 地点一覧を配列に格納
1645:     $zipfname = __DIR__ . '/'. self::FILE_ZIPNAME;
1646:     $zip = new ZipArchive;
1647:     if ($zip->open($zipfname) == TRUE) {
1648:         $this->str2array($zip->getFromName(self::FILE_C1), $this->c1);
1649:         $this->str2array($zip->getFromName(self::FILE_C2), $this->c2);
1650:         $this->str2array($zip->getFromName(self::FILE_INDEX), $this->index);
1651:         $zip->close();
1652:     } else {
1653:         $this->error = TRUE;
1654:         $this->errmsg = "cannot read \"{$zipfname}\"";
1655:     }
1656:     $zip = NULL;
1657: }
1658: 
1659: /**
1660:  * デストラクタ
1661:  * @return なし
1662: */
1663: function __destruct() {
1664:     unset($this->items);
1665: }
1666: 
1667: /**
1668:  * エラー状況
1669:  * @return bool TRUE:異常/FALSE:正常
1670: */
1671: function iserror() {
1672:     return $this->error;
1673: }
1674: 
1675: /**
1676:  * エラーメッセージ取得
1677:  * @param なし
1678:  * @return string 現在発生しているエラーメッセージ
1679: */
1680: function geterror() {
1681:     return $this->errmsg;
1682: }

潮汐と分潮

潮汐は、月と太陽の引力によって起きるわけだが、地球や月の公転軌道が真円ではないため、潮汐力は単純な振動方程式にならない。そこで、複数の正弦周期(サインカーブ)の和によって潮汐力の周期変動を表すことが行われている。
1 つ 1 つの周期を分潮 (ぶんちょう) と呼び、60種類が定義されている。このうち影響の大きい下記の 4 つを、主要4 分潮を呼ぶ。
M2月の引力によるもので、周期は約12時間25分。
S2太陽の引力によるもので、周期は約12時間。
O1月の引力によるもので、周期は約25時間49分。
K1観月と太陽の合成引力によるもので、周期は約23時間56分。

天文引数の計算

まず、周期計算のベースになる天文引数を求める。
 mimetex ‥‥(C.2)
 mimetex ‥‥(C.3)
 mimetex ‥‥(C.4)
 mimetex ‥‥(C.5)

 mimetex ‥‥(C.6)
 mimetex ‥‥(C.7)
Y西暦
Dその年の1月1日からの経過日数
L西暦2000年からその年の年初までの閏日の数

 mimetex 

{ }は中の数値を整数に丸めることを意味する。L は 2000 年以前では負数とする。

1718: /**
1719:  * 天文引数を求める
1720:  * @param int $year, $month, $day  グレゴリオ暦による年月日
1721:  * @return なし
1722: */
1723: function sun_moon($year$month$day) {
1724:     $y = (double)$year - 2000;
1725:     $L = (double)floor(($year + 3) / 4) - 500;
1726:     $d = (double)date('z', mktime(0, 0, 0, $month$day$year)) + $L;
1727: 
1728:     $this->_s = (double)211.728 + 129.38471 * $y + 13.176396 * $d;
1729:     $this->_h = (double)279.974 -   0.23871 * $y +  0.985647 * $d;
1730:     $this->_p = (double) 83.298 +  40.66229 * $y +  0.111404 * $d;
1731:     $this->_N = (double)125.071 -  19.32812 * $y -  0.052954 * $d;
1732: 
1733:     $this->_s = $this->_stdegree($this->_s);
1734:     $this->_h = $this->_stdegree($this->_h);
1735:     $this->_p = $this->_stdegree($this->_p);
1736:     $this->_N = $this->_stdegree($this->_N);
1737: }

分潮引数 Vi の計算

(C.1)式の  mimetex  は次のように表せる。

 mimetex ‥‥(C.9)
V0i世界時0時における分潮引数[tex:V_i]
σi分潮iの角速度で、後述する表-C.2から取得する。
n1日周期は1、半日周期は2、1/4周期は4‥‥となる定数で、後述する表-C.2から取得する。
tlローカル時間(時)。
ts世界時との時差(時)。日本標準時の場合は -9。
Lat観測地点の経度。東経はプラス、西経はマイナス。

基準となる  mimetex  は次のようにして計算できる。

 mimetex ‥‥(C.10)

 mimetex 、 mimetex 、 mimetex 、 mimetex  は、後述する表-C.2 から取得できる。

補正係数 fi、ui の計算

分潮によって  mimetex 、 mimetex  の計算方法は 3種類に分かれる。

1)L2、M1分潮
 mimetex ‥‥(C.17)
 mimetex ‥‥(C.18)

 mimetex ‥‥(C.19)
 mimetex ‥‥(C.20)

 mimetex ‥‥(C.21)

 mimetex ‥‥(C.22)

1739: /**
1740:  * L2, M1分潮のfi, uiの計算
1741:  * @param string $i  分潮記号 'L2' または 'M1'
1742:  * @param string $fn 'fi' または 'ui'
1743:  * @return double 計算結果
1744: */
1745: function fiui_L2M1($i$fn) {
1746:     //L2
1747:     if ($i == 'L2') {
1748:         $fcosu = (double)1 - 0.2505 * cos(deg2rad(2 * $this->_p))
1749:                 - 0.1102 * cos(deg2rad(2 * $this->_p - $this->_N))
1750:                 - 0.0156 * cos(deg2rad(2 * $this->_p - 2 * $this->_N))
1751:                 - 0.0370 * cos(deg2rad($this->_N));
1752:         $fsinu = (double)-0.2505 * sin(deg2rad(2 * $this->_p))
1753:                 - 0.1102 * sin(deg2rad(2 * $this->_p - $this->_N))
1754:                 - 0.0156 * sin(deg2rad(2 * $this->_p - 2 * $this->_N))
1755:                 - 0.0370 * sin(deg2rad($this->_N));
1756:     //M1
1757:     } else {
1758:         $fcosu = (double)2 * cos(deg2rad($this->_p))
1759:                 + 0.4 * cos(deg2rad($this->_p - $this->_N));
1760:         $fsinu = (double)sin(deg2rad($this->_p))
1761:                  + 0.2 * sin(deg2rad($this->_p - $this->_N));
1762:     }
1763: 
1764:     //fi
1765:     if ($fn == 'fi') {
1766:         $res = (double)sqrt($fcosu * $fcosu + $fsinu * $fsinu);
1767:     //ui
1768:     } else {
1769:         $res = (double)atan($fcosu / $fsinu);
1770:     }
1771: 
1772:     return $res;
1773: }

2)表-C.1 の 8 分潮
 mimetex ‥‥(C.11)
 mimetex ‥‥(C.12)


3)1,2 以外の分潮
表-C.2 の列に記載した計算式によって求める。

1775: /**
1776:  * fi, uiの計算
1777:  * @param string $i  分潮記号
1778:  * @param string $fn 'fi' または 'ui'
1779:  * @return double 計算結果
1780: */
1781: function fiui($i$fn) {
1782:     //L2 or M1
1783:     if (($i == 'L2') || ($i == 'M1')) {
1784:         $res = (double)$this->fiui_L2M1($i$fn);
1785: 
1786:     //fi
1787:     } else if ($fn == 'fi') {
1788:         //表-C.1による計算
1789:         if (isset($this->c1[$i])) {
1790:             $res = (double)$this->c1[$i][0]
1791:             + (double)$this->c1[$i][1] * cos(deg2rad($this->_N))
1792:             + (double)$this->c1[$i][2] * cos(2 * deg2rad($this->_N))
1793:             + (double)$this->c1[$i][3] * cos(3 * deg2rad($this->_N));
1794:         //表-C.2の参照
1795:         } else {
1796:             $res = $this->c2[$i][5];
1797:             if (! is_numeric($res))  $res = (double)$this->fiui($res$fn);
1798:         }
1799:     //ui
1800:     } else {
1801:         //表-C.1による計算
1802:         if (isset($this->c1[$i])) {
1803:             $res = (double)$this->c1[$i][4] * sin(deg2rad($this->_N))
1804:             + (double)$this->c1[$i][5] * sin(2 * deg2rad($this->_N))
1805:             + (double)$this->c1[$i][6] * sin(3 * deg2rad($this->_N));
1806:         //表-C.2の参照
1807:         } else {
1808:             $res = (double)$this->c2[$i][6];
1809:             if (! is_numeric($res))  $res = (double)$this->fiui($res$fn);
1810:         }
1811:     }
1812:     return $res;
1813: }

文字列として取得した計算式を評価し、結果の数値を出力するメソッド evalFiUi を用意した。

表-C.1、表-C.2

ダウンロードしたファイルを解凍して得られる "table_C1C2.xlsx" を元データとして、列区切りをタブ、行区切りを改行としたテキストファイル "_C1.txt" および "_C2.txt" を ZIP 圧縮ファイルに格納している。

1684: /**
1685:  * 表から配列へパラメータを格納する
1686:  * @param string $str パラメータ表
1687:  * @param array  $arr パラメータを格納する配列
1688:  * @return なし
1689: */
1690: function str2array($str, &$arr) {
1691:     $tok = strtok($str, "\n");
1692:     while ($tok != FALSE) {
1693:         $ss = trim($tok);
1694:         if ($ss == '') continue;
1695:         $cols = preg_split("/\t/iu", $ss);
1696:         $key = trim($cols[0]);
1697:         if (mb_substr($key, 0, 1) != self::COMMENT) {
1698:             for ($i = 1; $i < count($cols); $i++) {
1699:                 if (mb_substr($cols[$i], 0, 1) == self::COMMENT)  break;       //コメントから行末まで無視
1700:                 $arr[$key][$i - 1] = trim($cols[$i]);
1701:             }
1702:         }
1703:         $tok = strtok("\n");
1704:     }
1705: }

PHP プログラムでは、ZIP 圧縮ファイルからテキストを読み込み、計算しやすいように配列に格納する。
表-C.1 fi, Uiの係数
[海上保安庁(1992)]
分潮記号fiui
1cos Ncos 2Ncos 3Nsin Nsin 2Nsin 3N
Mm1.0000-0.13000.00130.00000.000.000.00
Mf1.04290.4135-0.00400.0000-23.742.68-0.38
O11.00890.1871-0.01470.001410.80-1.340.19
K11.00600.1150-0.00880.0006-8.860.68-0.07
J11.01290.1676-0.01700.0016-12.941.34-0.19
OO11.10270.65040.0317-0.0014-36.684.02-0.57
M21.0004-0.03730.00020.0000-2.140.000.00
K21.02410.28630.0083-0.0015-17.740.68-0.04
表-C.2 各文長の補正係数、角速度など
[海上保安庁(1992),気象庁(1999)]
分潮記号shpcnfiuiσ
(deg/hour)
period
(hour)
Sa01000100.04106868765.8211
Ssa02000100.08213734382.9052
Mm10-100MmMm0.5443747661.3092
MSf2-2000M2-M21.0158958354.3671
Mf20000MfMf1.0980331327.8590
2Q1-4122701O1O112.854286228.0062
σ1-4302701O1O112.927139827.8484
Q1-3112701O1O112.398660926.8684
ρ1-33-12701O1O113.471514526.7231
O1-2102701O1O113.943035625.8193
MP1-230901M2M214.025172925.6681
M1-110901M1M114.492052124.8412
χ1-13-1901J1J114.569547624.7091
π10-2019311014.917864724.1321
P10-1027011014.958931424.0659
S100018011015.000000024.0000
K1010901K1K115.041068623.9345
ψ102016711015.082135323.8693
φ10309011015.123205923.8045
θ11-11901J1J115.512589723.2070
J111-1901J1J115.585443323.0985
SO12-10901J1J116.056964422.4202
OO1210901OO1OO116.139101722.3061
OQ2-5211802O1*Q1O1+Q127.341696413.1667
MNS2-54102M2^22*M227.423833713.1273
2N2-42202M2M227.895354812.9054
μ2-44002M2M227.968208412.8718
N2-32102M2M228.439729512.6583
ν2-34-102M2M228.512583112.6260
OP2-2001802O1*P1O1+P128.901966912.4559
M2-22002M2M228.984104212.4206
MKS2-24002M2*K2M2+K229.066241512.3855
λ2-1011802M2M229.455625312.2218
L2-12-11802L2L229.528478912.1916
T20-1028321029.958933312.0164
S2000021030.000000012.0000
R201025721030.041066711.9836
K202002K2K230.082137311.9672
MSN210-102M2^22*M230.544374711.7861
KJ212-11802K1*J1K1+J130.626512011.7545
2SM22-2002M2-M231.015895811.6070
MO3-4302703M2*O1M2+O142.92713988.3863
M3-3301803M2^1.51.5*M243.47615638.2804
SO3-2102703O1O143.94303568.1924
MK3-230903M2*K1M2+K144.02517298.1771
SK3010903K1K145.04106867.9927
MN4-54104M2^22*M257.42383376.2692
M4-44004M2^22*M257.96820846.2103
SN4-32104M2M258.43972956.1602
MS4-22004M2M258.98410426.1033
MK4-24004M2*K2M2+K259.06624156.0949
S4000041060.00000006.0000
SK402004K2K260.08213735.9918
2MN6-76106M2^33*M286.40793804.1663
M6-66006M2^33*M286.95231274.1402
MSN6-54106M2^22*M287.42383374.1179
2MS6-44006M2^22*M287.96820844.0924
2MK6-46006M2^2*K22*M2+K288.05034574.0886
2SM6-22006M2M288.98410424.0457
MSK6-24006M2*K2M2+K289.06624154.0419

Zi、Hi、ui の入手

前述の通り、 mimetex 、 mimetex 、 mimetex  は観測地点の固有の値である。
これらの値は、気象庁の潮位表掲載地点一覧表から取得することができる。対象は、一覧表のうち、分潮一覧表の列のリンク先にコンテンツがあるもののみである。

これらのコンテンツを自動的に読み込んでゆき、各地点の分潮一覧表を ZIP 圧縮ファイルに格納していくプログラムが "makeTideDataFiles.php" である。

ある日の干潮、満潮時刻と潮位を求める

pahooTide::tide_level を繰り返すことで、指定地点のある日の干潮、満潮時刻と潮位を求めることができる。
干潮、満潮は、1 日に各々最大 4 回起きる。1 分ごとに潮位を計算し、上昇から下降へ転じる点が満潮時刻、その逆が干潮時刻である。だが、1 分ごとに計算すると、1 日で 60×24=1440 回も計算しなければならない。
計算回数を減らすために、まず 15 分ごとに計算することで変化点を検出し、そこから±15 分間を 1 分ごとに計算することで正確な時刻を求める。計算回数は、1 日で 4×24+15×8
=216 回に減らすことができる。

1932: /**
1933:  * 指定地点のある日の干潮、満潮時刻と潮位を求める
1934:  * @param string $code 地点記号
1935:  * @param int $year, $month, $day グレゴリオ暦による年月日
1936:  * @param array $items 結果を格納する配列
1937:  *              array('high'=>array('hh:mm',潮位), 'low'=>array('hh:mm',潮位))
1938:  * @return TRUE/FALSE
1939: */
1940: function tide_day($code$year$month$day, &$items) {
1941:     $this->setLocation($code);
1942:     if ($this->error)    return FALSE;
1943: 
1944:     $this->sun_moon($year$month$day);
1945: 
1946:     $interval = 15;              //第1段階刻み(分)
1947:     $td0 = $this->tide_level($year$month$dayself::JST, -$interval);
1948:     $flag = 0;
1949:     $cnt_high = 0;
1950:     $cnt_low = 0;
1951:     for ($i = 0; $i <= 24 * 60; $i += $interval) {
1952:         //第1段階刻み
1953:         $hh = floor($i / 60);
1954:         $mm = $i - $hh * 60;;
1955:         $td1 = $this->tide_level($year$month$day$hh + self::JST$mm);
1956:         if ($flag == 0) {
1957:             if ($td1 > $td0)    $flag = +1;       //上昇
1958:             else                $flag = -1;       //下降
1959:         } else if ($flag < 0) {
1960:             if ($td1 > $td0) {                       //上昇へ転じた
1961:                 //第2段階刻み
1962:                 $td0 = $this->tide_level($year$month$day$hh + self::JST$mm - $interval - 1);
1963:                 for ($j = -$interval$j < $interval$j++) {
1964:                     $hh = floor(($i + $j) / 60);
1965:                     $mm = ($i + $j) - $hh * 60;;
1966:                     $td1 = $this->tide_level($year$month$day$hh + self::JST$mm);
1967:                     if ($td1 > $td0) {               //上昇へ転じた
1968:                         $items['low'][$cnt_low]['hhmm'] = sprintf("%02d:%02d", $hh$mm);
1969:                         $items['low'][$cnt_low]['lev'] = (int)$td0;
1970:                         $cnt_low++;
1971:                         $flag = +1;
1972:                         break;
1973:                     }
1974:                     $td0 = $td1;
1975:                 }
1976:             }
1977:         } else {
1978:             if ($td1 < $td0) {                       //下降へ転じた
1979:                 //第2段階刻み
1980:                 $td0 = $this->tide_level($year$month$dayself::JST$mm - $interval - 1);
1981:                 for ($j = -$interval$j < $interval$j++) {
1982:                     $hh = floor(($i + $j) / 60);
1983:                     $mm = ($i + $j) - $hh * 60;;
1984:                     $td1 = $this->tide_level($year$month$day$hh + self::JST$mm);
1985:                     if ($td1 < $td0) {               //下降へ転じた
1986:                         $items['high'][$cnt_high]['hhmm'] = sprintf("%02d:%02d", $hh$mm);
1987:                         $items['high'][$cnt_high]['lev'] = (int)$td0;
1988:                         $cnt_high++;
1989:                         $flag = -1;
1990:                         break;
1991:                     }
1992:                     $td0 = $td1;
1993:                 }
1994:             }
1995:         }
1996:         $td0 = $td1;
1997:     }
1998:     return TRUE;
1999: }

解説:各種定数

ここからは、サンプル・プログラム本体 "tide.php" の解説をしてゆく。

0017: //jqPlotのあるフォルダ
0018: define('JQPLOT', '../../../../common/jqplot/');
0019: 
0020: //月の満ち欠け画像ファイルの場所
0021: // 画像作成方法→http://www.pahoo.org/e-soul/webtech/phpgd/phpgd-17-01.shtm
0022: define('MOONAGE', './moon/');
0023: 
0024: //プログラム・タイトル
0025: define('TITLE', '潮位・月齢を計算');
0026: 
0027: //リリース・フラグ(公開時にはTRUEにすること)
0028: define('FLAG_RELEASE', TRUE);
0029: 
0030: //リファラ・チェック(直リン防止用;空文字ならチェックしない)
0031: define('REFER_ON', 'www.pahoo.org');
0032: //define('REFER_ON', '');
0033: 
0034: //潮位表:計算期間(日)の初期値
0035: define('INTERVAL_DEF', 7);
0036: 
0037: //潮位グラフ:計算期間(日)の初期値
0038: define('INTERVAL_GRAPH_DEF', 5);
0039: 
0040: //潮位グラフのプロット間隔(日)
0041: define('INTERVAL_GRAPH', 0.02);
0042: 
0043: //潮位グラフの名前
0044: define('GRAPH_TIDE', 'jqPlot_tide');

これらの定数は自由に変更できる。
ただし、冒頭に述べたとおり計算量が大きいので、INTERVAL_DEFINTERVAL_GRAPH_DEF は、あまり大きな値にしない方がいいだろう。

潮位グラフを描くために、jQuery プラグイン「jqPlot」を利用する。使い方については、「PHP で NHK 政治意識月例調査をグラフ表示」を参照のこと。定数 JQPLOT の示すディレクトリに配置する。
また、月齢を示す月の画像は、定数 MOONAGE の示すディレクトリに配置する。圧縮ファイルに含まれている画像は、「PHP で月の満ち欠けを描画」で作成したモノだ。

解説:地点セレクタ

0358: /**
0359:  * 地点セレクタを作成する
0360:  * @param pahooTide $pt  潮位計算クラス
0361:  * @param string $code 地点記号
0362:  * @param string $errmsg エラーメッセージ格納用;エラーなければ空文字
0363:  * @return string HTML/FALSE:失敗
0364: */
0365: function makeJSlocation($pt$code) {
0366:     static $prefs = array(
0367: '--選択--','北海道', '青森県', '岩手県', '宮城県', '秋田県', '山形県', '福島県', 
0368: '茨城県', '栃木県', '群馬県', '埼玉県', '千葉県', '東京都', '神奈川県', 
0369: '新潟県', '富山県', '石川県', '福井県', '山梨県', '長野県', '岐阜県', '静岡県', 
0370: '愛知県', '三重県', '滋賀県', '京都府', '大阪府', '兵庫県', '奈良県',
0371: '和歌山県', '鳥取県', '島根県', '岡山県', '広島県', '山口県', '徳島県',
0372: '香川県', '愛媛県', '高知県', '福岡県', '佐賀県', '長崎県', '熊本県', '大分県', 
0373: '宮崎県', '鹿児島県', '沖縄県');
0374: 
0375:     if ($code == '') {
0376:         $pp = '';
0377:     } else if (! isset($pt->index[$code])) {
0378:         $errmsg = "not exist location '{$code}'";
0379:         return FALSE;
0380:     } else {
0381:         $pp = $pt->index[$code][4];
0382:     }
0383: 
0384:     //都道府県
0385:     $js = "var pref = [\n";
0386:     foreach ($prefs as $key=>$val) {
0387:         $selected = ($val == $pp) ? 'selected' : '';
0388:         $js .= "{'type':{$key}, 'text':'{$val}', 'value':'{$val}', 'selected':'{$selected}'},\n";
0389:     }
0390:     //地点
0391:     $js .= "];\nvar loc = [\n";
0392:     foreach ($prefs as $key=>$val) {
0393:         $js .= "{'type':{$key}, 'text':'--選択--', 'value':''},\n";
0394:         foreach ($pt->index as $cd=>$arr) {
0395:             if ($arr[4] == $val) {
0396:                 $selected = ($cd == $code) ? 'selected' : '';
0397:                 $js .= "{'type':{$key}, 'text':'{$arr[0]}', 'value':'{$cd}', 'selected':'{$selected}'},\n";
0398:             }
0399:         }
0400:     }
0401: 
0402:     //スクリプト
0403: $js .=<<< EOD
0404: ];
0405: 
0406: $(function() {
0407:     var html = '';
0408:     for (var i in pref) {
0409:         html += '<option value="'+ pref[i].value + '" ' + pref[i].selected + '>'+ pref[i].text +'</option>';
0410:     }
0411:     $('#pref').append(html);
0412: 
0413:     var html = '';
0414:     var index = $('#pref option:selected').index();
0415:     for (var i in loc) {
0416:         if (loc[i].type == index) {
0417:             html += '<option value="'+ loc[i].value + '" ' + loc[i].selected + '>' + loc[i].text +'</option>';
0418:         }
0419:     }
0420:     $('#code').append(html);
0421: 
0422:     //都道府県を選択した時の挙動
0423:     $('#pref').change(function() {
0424:         var html = '';
0425:         var index = $('#pref option:selected').index();
0426:         //地点の内容を削除
0427:         $('#code').empty();
0428:         //対応する地点の内容を表示
0429:         for (var i in loc) {
0430:             if (loc[i].type == index) {
0431:                 html += '<option value="'+ loc[i].value +'">'+ loc[i].text +'</option>';
0432:             }
0433:         }
0434:         $('#code').append(html);
0435:     });
0436: });
0437: 
0438: EOD;
0439:     return $js;
0440: }

観測地点は、都道府県のセレクタと、観測地点のセレクタによって選択する。
観測地点セレクタを都道府県セレクタに連動させるために、jQuery を利用した。

解説:潮位表の計算と作成

0443: /**
0444:  * 潮位表:潮位・月齢を計算する
0445:  * @param pahooTide $pt  潮位計算クラス
0446:  * @param pahooCalendar $pc  暦計算クラス
0447:  * @param array  $inputs 計算用パラメータ
0448:  * @param array  $items  計算結果格納用配列
0449:  * @param array  $locs   地点情報格納用配列
0450:  * @param string $errmsg エラーメッセージ格納用;エラーなければ空文字
0451:  * @return bool TRUE:計算成功/FALSE:失敗
0452: */
0453: function calcTideTable($pt$pc$params, &$items, &$locs, &$errmsg) {
0454:     //地点を設定
0455:     $pt->setLocation($params['code']);
0456:     if ($pt->iserror())  return FALSE;
0457: 
0458:     //地点を取得
0459:     $pt->getLocation($params['code'], $locs);
0460:     if ($pt->iserror())  return FALSE;
0461: 
0462:     //月齢・潮位を計算
0463:     $res = TRUE;
0464:     $jd = $pc->Gregorian2JD($params['year'], $params['month'], $params['day'], 0, 0, 0);
0465:     for ($i = 0; $i < $params['interval']; $i++) {
0466:         $arr = array();
0467:         list($year$month$day$hour$min$sec) = $pc->JD2Gregorian($jd);
0468:         $yb = $pc->getWeekString($year$month$day);
0469:         $items[$i]['dt'] = sprintf("%04d-%02d-%02d(%s)", $year$month$day$yb);
0470:         $items[$i]['moonage'] = $pc->moon_age($year$month$dayMOONAGE_HOUR, 0, 0);
0471:         $moonmeridian = $pc->moon_time(2, $params['longitude'], $params['latitude'], $params['height'], $year$month$day);
0472:         $moonmeridian = ($moonmeridian == FALSE) ? '---' : $pc->day2hhmm($moonmeridian);
0473:         $items[$i]['tide'] = (preg_match('/([0-9]+)\:([0-9]+)/', $moonmeridian$arr) > 0) ? $pc->tide($year$month$day$arr[1]$arr[2], 0) : '';
0474:         unset($arr);
0475: 
0476:         $arr = array();
0477:         $pt->tide_day($params['code'], $year$month$day$arr);
0478:         //満潮
0479:         for ($j = 0; $j < 4; $j++) {
0480:             if (isset($arr['high'][$j])) {
0481:                 $items[$i]['high'][$j]['hhmm']  = $arr['high'][$j]['hhmm'];
0482:                 $items[$i]['high'][$j]['lev']   = sprintf("%d", $arr['high'][$j]['lev']);
0483:                 $items[$i]['high'][$j]['align'] = 'text-align:right;';
0484:             } else {
0485:                 $items[$i]['high'][$j]['hhmm']  = '*';
0486:                 $items[$i]['high'][$j]['lev']   = '*';
0487:                 $items[$i]['high'][$j]['align'] = 'text-align:center;';
0488:             }
0489:         }
0490:         //干潮
0491:         for ($j = 0; $j < 4; $j++) {
0492:             if (isset($arr['low'][$j])) {
0493:                 $items[$i]['low'][$j]['hhmm']  = $arr['low'][$j]['hhmm'];
0494:                 $items[$i]['low'][$j]['lev']   = sprintf("%d", $arr['low'][$j]['lev']);
0495:                 $items[$i]['low'][$j]['align'] = 'text-align:right;';
0496:             } else {
0497:                 $items[$i]['low'][$j]['hhmm']  = '*';
0498:                 $items[$i]['low'][$j]['lev']   = '*';
0499:                 $items[$i]['low'][$j]['align'] = 'text-align:center;';
0500:             }
0501:         }
0502:         $jd++;
0503:     }
0504: 
0505:     return $res;
0506: }

計算開始年月日と期間を指定して、tide::tide_day を繰り返し実行して満潮・干潮を計算してゆくのがユーザー関数 calcTideTable である。入力パラメータが多いので、配列で渡す。
月齢・潮の計算は、「PHP で日出没・月出没・月齢・潮を計算」で紹介したとおりである。

0508: /**
0509:  * 潮位表:表示用HTMLを作成
0510:  * @param pahooTide     $pt      潮位計算クラス
0511:  * @param pahooCalendar $pc      暦計算クラス
0512:  * @param array         $params  計算用パラメータ
0513:  * @param string        $js      スクリプト格納用
0514:  * @param string        $html    HTML格納用
0515:  * @param string        $errmsg  エラーメッセージ格納用
0516:  * @return bool TRUE:成功/FALSE:失敗
0517:  * @return 表示用HTML
0518: */
0519: function makeTable($pt$pc$params, &$js, &$html, &$errmsg) {
0520:     $js = $html = '';
0521:     $items = array();
0522:     $locs  = array();
0523:     if (calcTideTable($pt$pc$params$items$locs$errmsg) == FALSE)  return FALSE;
0524: 
0525:     $hour = MOONAGE_HOUR;
0526: $html =<<< EOD
0527: <table class="tide">
0528: <caption>{$locs['title']}({$locs['prefecture']}{$locs['address']})</caption>
0529: <tr><th rowspan="2">年月日</th><th rowspan="2">月齢<br /><span style="font-size:small; font-weight:normal;">{$hour}時</span></th><th rowspan="2">潮</th><th colspan="8">満潮</th><th colspan="8">干潮</th></tr>
0530: 
0531: EOD;
0532:     $html .= "<tr>";
0533:     for ($j = 0; $j < 8; $j++) {
0534:         $html .= "<th class=\"index\">時刻</th><th class=\"index\">潮位<br />(cm)</th>";
0535:     }
0536:     $html .= "</tr>\n";
0537:     foreach ($items as $item) {
0538:         $html .= sprintf("<tr><td>%s</td><td>%.1f</td><td>%s</td>", $item['dt'], $item['moonage'], $item['tide']);
0539:         for ($j = 0; $j < 4; $j++) {
0540:             $html .= sprintf("<td>%s</td><td style=\"%s\">%s</td>", $item['high'][$j]['hhmm'], $item['high'][$j]['align'], $item['high'][$j]['lev']);
0541:         }
0542:         for ($j = 0; $j < 4; $j++) {
0543:             $html .= sprintf("<td>%s</td><td style=\"%s\">%s</td>", $item['low'][$j]['hhmm'], $item['low'][$j]['align'], $item['low'][$j]['lev']);
0544:         }
0545:         $html .= "</tr>\n";
0546:     }
0547:     $html .= "</table>\n";
0548: 
0549:     return TRUE;
0550: }

calcTideTable を呼び出し、ブラウザ上に潮位表を表示するためのユーザー関数が makeTable である。

解説:潮位グラフの計算と作成

0552: /**
0553:  * 潮位グラフ:潮位・月齢を計算する
0554:  * @param pahooTide $pt  潮位計算クラス
0555:  * @param pahooCalendar $pc  暦計算クラス
0556:  * @param array  $params 計算用パラメータ
0557:  * @param array  $items  潮位計算結果格納用配列
0558:  * @param array  $moons  月齢計算結果格納用配列
0559:  * @param array  $locs   地点情報格納用配列
0560:  * @param string $errmsg エラーメッセージ格納用;エラーなければ空文字
0561:  * @return bool TRUE:計算成功/FALSE:失敗
0562: */
0563: function calcTideGraph($pt$pc$params, &$items, &$moons, &$locs, &$errmsg) {
0564:     //地点を設定
0565:     $pt->setLocation($params['code']);
0566:     if ($pt->iserror())  return FALSE;
0567: 
0568:     //地点を取得
0569:     $pt->getLocation($params['code'], $locs);
0570:     if ($pt->iserror())  return FALSE;
0571: 
0572:     $res = TRUE;
0573: 
0574:     //潮位を計算
0575:     $jd = $pc->Gregorian2JD($params['year'], $params['month'], $params['day'], -JST, 0, 0);
0576:     $n = $params['interval'] / INTERVAL_GRAPH;
0577:     for ($i = 0; $i < $n$i++) {
0578:         list($year$month$day$hour$min$sec) = $pc->JD2Gregorian($jd + JST / 24);
0579:         $items[$i]['dt'] = sprintf("%04d/%02d/%02d %02d:%02d", $year$month$day$hour$min);
0580:         list($year$month$day$hour$min$sec) = $pc->JD2Gregorian($jd);
0581:         $pt->sun_moon($year$month$day);
0582:         $items[$i]['tlevel'] = $pt->tide_level($year$month$day$hour$min);
0583:         $jd += INTERVAL_GRAPH;
0584:     }
0585: 
0586:     //月齢を計算
0587:     $height = 0;     //標高0メートルを仮定
0588:     $n = $params['interval'];
0589:     $jd = $pc->Gregorian2JD($params['year'], $params['month'], $params['day'], 0, 0, 0);
0590:     $i = $d = 0;
0591:     while ($i < $n) {
0592:         //月の南中時刻
0593:         list($year$month$day$hour$min$sec) = $pc->JD2Gregorian($jd);
0594:         $moonmeridian = $pc->moon_time(2, $params['longitude'], $params['latitude'], $height$year$month$day);
0595:         if ($moonmeridian != FALSE) {
0596:             $x = (double)$i + $moonmeridian;
0597:             $moons[$i]['t']   = $d + $moonmeridian;
0598:             $moons[$i]['age'] = $pc->moon_age($year$month$day$moonmeridian * 24, 0, 0);
0599:             $i++;
0600:         }
0601:         $jd++;
0602:         $d++;
0603:     }
0604: 
0605:     return $res;
0606: }

ユーザー関数 calcTideGraph はメソッド pahooTide::tide_level を呼び出し、指定日時における潮位を配列 $items に格納してゆく。この配列を、「PHP で NHK 政治意識月例調査をグラフ表示」で紹介した jQuery プラグイン「jqPlot」を用いてグラフ表示する。

月齢は、月の画像をグラフ上にオーバーライトすることを目指す。プロットする場所は、月の南中時刻とすべく、メソッド pahooCalendar::moon_time によって南中時刻を求める。

0608: /**
0609:  * 潮位グラフ:表示用スクリプトおよびHTMLを作成
0610:  * @param pahooTide     $pt      潮位計算クラス
0611:  * @param pahooCalendar $pc      暦計算クラス
0612:  * @param array         $params  計算用パラメータ
0613:  * @param string        $js      スクリプト格納用
0614:  * @param string        $html    HTML格納用
0615:  * @param string        $errmsg  エラーメッセージ格納用
0616:  * @return bool TRUE:成功/FALSE:失敗
0617: */
0618: function makeGraph($pt$pc$params, &$js, &$html, &$errmsg) {
0619:     $js = $html = '';
0620:     $items = array();
0621:     $moons = array();
0622:     $locs  = array();
0623:     if (calcTideGraph($pt$pc$params$items$moons$locs$errmsg) == FALSE)  return FALSE;
0624: 
0625:     $title = "{$locs['title']}({$locs['prefecture']}{$locs['address']})";
0626:     $name   = GRAPH_TIDE;
0627:     $width  = MAP_WIDTH;
0628:     $height = MAP_HEIGHT;
0629: 
0630:     //潮位プロット・データ作成
0631:     $data = '';
0632:     foreach ($items as $item) {
0633:         $data .= sprintf("['%s', %f], ", $item['dt'], $item['tlevel']);
0634:     }
0635: 
0636:     //月齢データ作成
0637:     $moon = '';
0638:     $path = MOONAGE;
0639:     foreach ($moons as $val) {
0640:         $x = round(30 + (MAP_WIDTH - 70) / $params['interval'] * $val['t']);
0641:         $fname = sprintf("{$path}moon_%02d.png", round($val['age']));
0642: $moon .=<<< EOD
0643: <img style="position:absolute; top:35px; left:{$x}px; width:40px; height:40px; z-index:999;" src="{$fname}" />
0644: 
0645: EOD;
0646:     }
0647: 
0648: $js =<<< EOD
0649: $(function() {
0650:     jQuery.jqplot('{$name}',
0651:     [
0652:         [ {$data} ]
0653:     ],
0654:     {
0655:         //タイトル
0656:         title: {
0657:             text: '{$title}',
0658:             show: true,
0659:             fontSize: '16px',
0660:             textAlign: 'center',
0661:             textColor: 'black'
0662:         },
0663:         //背景
0664:         grid: {
0665:             background: '#EEFFFF'
0666:         },
0667:         //グラフ
0668:         seriesDefaults: {
0669:             showLine: true,
0670:             rendererOptions: { smooth: false },
0671:             markerOptions: { size: 0 },
0672:             color: 'blue',
0673:         },
0674:         //軸ラベル
0675:         axes: {
0676:             xaxis: {
0677:                 renderer: $.jqplot.DateAxisRenderer,
0678:                 tickOptions: { formatString: '%m/%d' },
0679:                 tickInterval: '1 days'
0680:             },
0681:             yaxis: {
0682:                 label: '潮位(cm)'
0683:             }
0684:         }
0685:     }
0686:     );
0687: });
0688: 
0689: EOD;
0690: $html =<<< EOD
0691: <div id="{$name}" style="width:{$width}px; height:{$height}px;">
0692: {$moon}
0693: </div>
0694: 
0695: EOD;
0696: 
0697:     return TRUE;
0698: }

calcTideGraph を呼び出し、ブラウザ上に潮位グラフを表示するためのユーザー関数が makeGraph である。
jqPlot に渡すスクリプトと、表示する HTML は別々に生成する。jqPlot で画像を扱えないため、無理をして HTML 側でグラフ上にオーバーライトしている。

解説:メインプログラム

メインプログラムは、これまで紹介してきたものを流用している。
Google マップの表示や住所検索については「PHP で Google等を利用して住所から緯度・経度を求める」を、UI:Datepicker を利用した日付入力については「PHP で日付入力:カレンダーから選択」を参照してほしい。

参考サイト

(この項おわり)
header