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


目次
サンプル・プログラムのダウンロード
tide.php | サンプル・プログラム本体。 |
pahooCalendar.php | 暦計算クラス pahooCalendar、潮位計算クラス pahooTide。 include_pathが通ったディレクトリに配置すること。 |
tideData.zip | 計算パラメータ。pahooTideが解凍せずに参照する圧縮ファイル。 |
pahooGeoCode.php | 住所・緯度・経度に関わるクラス pahooGeoCode。 include_pathが通ったディレクトリに配置すること。 |
moon/moon_00.png~moon_30.png | 月齢画像。 |
makeTideDataFiles.php | tideData.zip を自動生成する。 サンプル・プログラム実行時には不要。 |
table_C1C2.xlsx | 表-C.1, C.2の元データ。 サンプル・プログラム実行時には不要。 |
dispC1C2table.php | 表-C.1, C.2をTABLE表示する。 サンプル・プログラム実行時には不要。 |
準備:外部クラスなど
Google マップの表示や住所検索には、ユーザークラス "pahooGeoCode" を利用する。クラスファイル "pahooGeoCode.php" を include_path が通ったディレクトリに配置すること。
また、ユーザークラス "pahooTide" が参照するデータファイル "tideData.zip" はクラスファイルと同じディレクトリに配置すること。
全体の流れ

潮汐は複数の分潮の合成であり、下記の式で求めることができる。

η | 潮位 |
Z0 | 観測値の平均水面 |
fi | 分潮iの振幅に対する補正係数 |
Hi | 観測地において計算された振幅 |
Vi | 時間とともに変化する分潮引数 |
ui | 時間とともに変化する位相の補正係数 |
ti | 観測地において計算された位相遅れ |




1871: /**
1872: * 現在設定されている地点の潮位を求める
1873: * @param int $year, $month, $day グレゴリオ暦による年月日
1874: * @param double $hour, $min 時分(世界時)
1875: * @return double 潮位
1876: */
1877: function tide_level($year, $month, $day, $hour, $min) {
1878: $tt = (double)$hour + $min / 60;
1879: $td = (double)0;
1880: foreach ($this->keys as $i) {
1881: $V0 = (double)$this->c2[$i][0] * $this->_s
1882: + (double)$this->c2[$i][1] * $this->_h
1883: + (double)$this->c2[$i][2] * $this->_p
1884: + (double)$this->c2[$i][3];
1885: $V0 = (double)$this->_stdegree($V0);
1886: $fi = (double)$this->evalFiUi($this->c2[$i][5], 'fi');
1887: $ui = (double)$this->evalFiUi($this->c2[$i][6], 'ui');
1888: $Viui = $V0 + $ui + (double)$this->c2[$i][4] * (double)$this->port['longitude'][0] + (double)$this->c2[$i][7] * $tt - (double)$this->port[$i][1];
1889: $Viui = $this->_stdegree($Viui);
1890: $d = $fi * (double)$this->port[$i][0] * cos(deg2rad($Viui));
1891: $td += $d;
1892: }
1893:
1894: return (double)$this->port['base'][0] + $td;
1895: }
潮汐と分潮
1 つ 1 つの周期を分潮と呼び、60種類が定義されている。このうち影響の大きい下記の 4 つを、主要4 分潮を呼ぶ。
M2 | 月の引力によるもので、周期は約12時間25分。 |
S2 | 太陽の引力によるもので、周期は約12時間。 |
O1 | 月の引力によるもので、周期は約25時間49分。 |
K1 | 観月と太陽の合成引力によるもので、周期は約23時間56分。 |
天文引数の計算






Y | 西暦 |
D | その年の1月1日からの経過日数 |
L | 西暦2000年からその年の年初までの閏日の数 |

{ }は中の数値を整数に丸めることを意味する。L は 2000 年以前では負数とする。
1672: /**
1673: * 角度を0以上360未満に正規化する
1674: * @param double $d 角度
1675: * @return double 正規化した角度
1676: */
1677: function _stdegree($d) {
1678: while ($d < 0) $d += (double)360.0;
1679: while ($d >= 360.0) $d -= (double)360.0;
1680: return (double)$d;
1681: }
1682:
1683: /**
1684: * 天文引数を求める
1685: * @param int $year, $month, $day グレゴリオ暦による年月日
1686: * @return なし
1687: */
1688: function sun_moon($year, $month, $day) {
1689: $y = (double)$year - 2000;
1690: $L = (double)floor(($year + 3) / 4) - 500;
1691: $d = (double)date('z', mktime(0, 0, 0, $month, $day, $year)) + $L;
1692:
1693: $this->_s = (double)211.728 + 129.38471 * $y + 13.176396 * $d;
1694: $this->_h = (double)279.974 - 0.23871 * $y + 0.985647 * $d;
1695: $this->_p = (double) 83.298 + 40.66229 * $y + 0.111404 * $d;
1696: $this->_N = (double)125.071 - 19.32812 * $y - 0.052954 * $d;
1697:
1698: $this->_s = $this->_stdegree($this->_s);
1699: $this->_h = $this->_stdegree($this->_h);
1700: $this->_p = $this->_stdegree($this->_p);
1701: $this->_N = $this->_stdegree($this->_N);
1702: }
分潮引数 Vi の計算


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






補正係数 fi、ui の計算



1)L2、M1分潮






1704: /**
1705: * L2, M1分潮のfi, uiの計算
1706: * @param string $i 分潮記号 'L2' または 'M1'
1707: * @param string $fn 'fi' または 'ui'
1708: * @return double 計算結果
1709: */
1710: function fiui_L2M1($i, $fn) {
1711: //L2
1712: if ($i == 'L2') {
1713: $fcosu = (double)1 - 0.2505 * cos(deg2rad(2 * $this->_p))
1714: - 0.1102 * cos(deg2rad(2 * $this->_p - $this->_N))
1715: - 0.0156 * cos(deg2rad(2 * $this->_p - 2 * $this->_N))
1716: - 0.0370 * cos(deg2rad($this->_N));
1717: $fsinu = (double)-0.2505 * sin(deg2rad(2 * $this->_p))
1718: - 0.1102 * sin(deg2rad(2 * $this->_p - $this->_N))
1719: - 0.0156 * sin(deg2rad(2 * $this->_p - 2 * $this->_N))
1720: - 0.0370 * sin(deg2rad($this->_N));
1721: //M1
1722: } else {
1723: $fcosu = (double)2 * cos(deg2rad($this->_p))
1724: + 0.4 * cos(deg2rad($this->_p - $this->_N));
1725: $fsinu = (double)sin(deg2rad($this->_p))
1726: + 0.2 * sin(deg2rad($this->_p - $this->_N));
1727: }
1728:
1729: //fi
1730: if ($fn == 'fi') {
1731: $res = (double)sqrt($fcosu * $fcosu + $fsinu * $fsinu);
1732: //ui
1733: } else {
1734: $res = (double)atan($fcosu / $fsinu);
1735: }
1736:
1737: return $res;
1738: }



3)1,2 以外の分潮
表-C.2 の列に記載した計算式によって求める。
1740: /**
1741: * fi, uiの計算
1742: * @param string $i 分潮記号
1743: * @param string $fn 'fi' または 'ui'
1744: * @return double 計算結果
1745: */
1746: function fiui($i, $fn) {
1747: //L2 or M1
1748: if (($i == 'L2') || ($i == 'M1')) {
1749: $res = (double)$this->fiui_L2M1($i, $fn);
1750:
1751: //fi
1752: } else if ($fn == 'fi') {
1753: //表-C.1による計算
1754: if (isset($this->c1[$i])) {
1755: $res = (double)$this->c1[$i][0]
1756: + (double)$this->c1[$i][1] * cos(deg2rad($this->_N))
1757: + (double)$this->c1[$i][2] * cos(2 * deg2rad($this->_N))
1758: + (double)$this->c1[$i][3] * cos(3 * deg2rad($this->_N));
1759: //表-C.2の参照
1760: } else {
1761: $res = $this->c2[$i][5];
1762: if (! is_numeric($res)) $res = (double)$this->fiui($res, $fn);
1763: }
1764: //ui
1765: } else {
1766: //表-C.1による計算
1767: if (isset($this->c1[$i])) {
1768: $res = (double)$this->c1[$i][4] * sin(deg2rad($this->_N))
1769: + (double)$this->c1[$i][5] * sin(2 * deg2rad($this->_N))
1770: + (double)$this->c1[$i][6] * sin(3 * deg2rad($this->_N));
1771: //表-C.2の参照
1772: } else {
1773: $res = (double)$this->c2[$i][6];
1774: if (! is_numeric($res)) $res = (double)$this->fiui($res, $fn);
1775: }
1776: }
1777: return $res;
1778: }
1779:
1780: /**
1781: * fi, uiの式の評価
1782: * @param string $str 式
1783: * @param string $fn 'fi' または 'ui'
1784: * @return double 計算結果
1785: */
1786: function evalFiUi($str, $fn) {
1787: //定数
1788: if (is_numeric($str)) return (double)$str;
1789:
1790: //式の分解
1791: $stack = array();
1792: $ptr = 0;
1793: $cc = '';
1794: $i = 0;
1795: while ($i < mb_strlen($str)) {
1796: $c = mb_substr($str, $i, 1);
1797: //演算子
1798: if (preg_match("/[\+\-\*\/\^]/ui", $c) > 0) {
1799: //負の符号
1800: if (($ptr == 0) && ($c == '-')) {
1801: $stack[$ptr] = $c;
1802: //定数
1803: } else if (preg_match("/^[0-9\.]+$/ui", $cc) > 0) {
1804: $stack[$ptr] = $cc;
1805: $ptr++;
1806: //変数
1807: } else {
1808: $stack[$ptr] = "\$this->fiui('{$cc}', '{$fn}')";
1809: $ptr++;
1810: }
1811: $stack[$ptr] = $c;
1812: $ptr++;
1813: $cc = '';
1814: //定数・変数
1815: } else if (preg_match("/[A-Z|0-9\.]+/ui", $c) > 0) {
1816: $cc .= $c;
1817: }
1818: $i++;
1819: }
1820: //定数・変数
1821: if (preg_match("/^[0-9\.]+$/ui", $cc) > 0) {
1822: $stack[$ptr] = $cc;
1823: //変数
1824: } else {
1825: $stack[$ptr] = "\$this->fiui('{$cc}', '{$fn}')";
1826: }
1827:
1828: //式の再構築
1829: $ee = '';
1830: $ptr = 0;
1831: while ($ptr < count($stack)) {
1832: //負の符号
1833: if (($ptr == 0) && ($stack[$ptr] == '-')) {
1834: $stack[$ptr + 1] = $stack[$ptr] . $stack[$ptr + 1];
1835: $stack[$ptr] = '';
1836: $ptr++;
1837: //べき乗演算子
1838: } else if ($stack[$ptr] == '^') {
1839: $stack[$ptr + 1] = 'pow(' . $stack[$ptr - 1] . ',' . $stack[$ptr + 1] . ')';
1840: $ptr++;
1841: //四則演算子
1842: } else if (preg_match("/^[\+\-\*\/]$/ui", $stack[$ptr]) > 0) {
1843: $stack[$ptr + 1] = $stack[$ptr - 1] . $stack[$ptr] . $stack[$ptr + 1];
1844: $ptr++;
1845: }
1846: $ptr++;
1847: }
1848: $res = (double)eval('return ' . $stack[$ptr - 1] . ';');
1849:
1850: return $res;
1851: }
表-C.1、表-C.2
1649: /**
1650: * 表から配列へパラメータを格納する
1651: * @param string $str パラメータ表
1652: * @param array $arr パラメータを格納する配列
1653: * @return なし
1654: */
1655: function str2array($str, &$arr) {
1656: $tok = strtok($str, "\n");
1657: while ($tok != FALSE) {
1658: $ss = trim($tok);
1659: if ($ss == '') continue;
1660: $cols = preg_split("/\t/iu", $ss);
1661: $key = trim($cols[0]);
1662: if (mb_substr($key, 0, 1) != self::COMMENT) {
1663: for ($i = 1; $i < count($cols); $i++) {
1664: if (mb_substr($cols[$i], 0, 1) == self::COMMENT) break; //コメントから行末まで無視
1665: $arr[$key][$i - 1] = trim($cols[$i]);
1666: }
1667: }
1668: $tok = strtok("\n");
1669: }
1670: }
分潮記号 | fi | ui | |||||
---|---|---|---|---|---|---|---|
1 | cos N | cos 2N | cos 3N | sin N | sin 2N | sin 3N | |
Mm | 1.0000 | -0.1300 | 0.0013 | 0.0000 | 0.00 | 0.00 | 0.00 |
Mf | 1.0429 | 0.4135 | -0.0040 | 0.0000 | -23.74 | 2.68 | -0.38 |
O1 | 1.0089 | 0.1871 | -0.0147 | 0.0014 | 10.80 | -1.34 | 0.19 |
K1 | 1.0060 | 0.1150 | -0.0088 | 0.0006 | -8.86 | 0.68 | -0.07 |
J1 | 1.0129 | 0.1676 | -0.0170 | 0.0016 | -12.94 | 1.34 | -0.19 |
OO1 | 1.1027 | 0.6504 | 0.0317 | -0.0014 | -36.68 | 4.02 | -0.57 |
M2 | 1.0004 | -0.0373 | 0.0002 | 0.0000 | -2.14 | 0.00 | 0.00 |
K2 | 1.0241 | 0.2863 | 0.0083 | -0.0015 | -17.74 | 0.68 | -0.04 |
分潮記号 | s | h | p | c | n | fi | ui | σ (deg/hour) | period (hour) |
---|---|---|---|---|---|---|---|---|---|
Sa | 0 | 1 | 0 | 0 | 0 | 1 | 0 | 0.0410686 | 8765.8211 |
Ssa | 0 | 2 | 0 | 0 | 0 | 1 | 0 | 0.0821373 | 4382.9052 |
Mm | 1 | 0 | -1 | 0 | 0 | Mm | Mm | 0.5443747 | 661.3092 |
MSf | 2 | -2 | 0 | 0 | 0 | M2 | -M2 | 1.0158958 | 354.3671 |
Mf | 2 | 0 | 0 | 0 | 0 | Mf | Mf | 1.0980331 | 327.8590 |
2Q1 | -4 | 1 | 2 | 270 | 1 | O1 | O1 | 12.8542862 | 28.0062 |
σ1 | -4 | 3 | 0 | 270 | 1 | O1 | O1 | 12.9271398 | 27.8484 |
Q1 | -3 | 1 | 1 | 270 | 1 | O1 | O1 | 12.3986609 | 26.8684 |
ρ1 | -3 | 3 | -1 | 270 | 1 | O1 | O1 | 13.4715145 | 26.7231 |
O1 | -2 | 1 | 0 | 270 | 1 | O1 | O1 | 13.9430356 | 25.8193 |
MP1 | -2 | 3 | 0 | 90 | 1 | M2 | M2 | 14.0251729 | 25.6681 |
M1 | -1 | 1 | 0 | 90 | 1 | M1 | M1 | 14.4920521 | 24.8412 |
χ1 | -1 | 3 | -1 | 90 | 1 | J1 | J1 | 14.5695476 | 24.7091 |
π1 | 0 | -2 | 0 | 193 | 1 | 1 | 0 | 14.9178647 | 24.1321 |
P1 | 0 | -1 | 0 | 270 | 1 | 1 | 0 | 14.9589314 | 24.0659 |
S1 | 0 | 0 | 0 | 180 | 1 | 1 | 0 | 15.0000000 | 24.0000 |
K1 | 0 | 1 | 0 | 90 | 1 | K1 | K1 | 15.0410686 | 23.9345 |
ψ1 | 0 | 2 | 0 | 167 | 1 | 1 | 0 | 15.0821353 | 23.8693 |
φ1 | 0 | 3 | 0 | 90 | 1 | 1 | 0 | 15.1232059 | 23.8045 |
θ1 | 1 | -1 | 1 | 90 | 1 | J1 | J1 | 15.5125897 | 23.2070 |
J1 | 1 | 1 | -1 | 90 | 1 | J1 | J1 | 15.5854433 | 23.0985 |
SO1 | 2 | -1 | 0 | 90 | 1 | J1 | J1 | 16.0569644 | 22.4202 |
OO1 | 2 | 1 | 0 | 90 | 1 | OO1 | OO1 | 16.1391017 | 22.3061 |
OQ2 | -5 | 2 | 1 | 180 | 2 | O1*Q1 | O1+Q1 | 27.3416964 | 13.1667 |
MNS2 | -5 | 4 | 1 | 0 | 2 | M2^2 | 2*M2 | 27.4238337 | 13.1273 |
2N2 | -4 | 2 | 2 | 0 | 2 | M2 | M2 | 27.8953548 | 12.9054 |
μ2 | -4 | 4 | 0 | 0 | 2 | M2 | M2 | 27.9682084 | 12.8718 |
N2 | -3 | 2 | 1 | 0 | 2 | M2 | M2 | 28.4397295 | 12.6583 |
ν2 | -3 | 4 | -1 | 0 | 2 | M2 | M2 | 28.5125831 | 12.6260 |
OP2 | -2 | 0 | 0 | 180 | 2 | O1*P1 | O1+P1 | 28.9019669 | 12.4559 |
M2 | -2 | 2 | 0 | 0 | 2 | M2 | M2 | 28.9841042 | 12.4206 |
MKS2 | -2 | 4 | 0 | 0 | 2 | M2*K2 | M2+K2 | 29.0662415 | 12.3855 |
λ2 | -1 | 0 | 1 | 180 | 2 | M2 | M2 | 29.4556253 | 12.2218 |
L2 | -1 | 2 | -1 | 180 | 2 | L2 | L2 | 29.5284789 | 12.1916 |
T2 | 0 | -1 | 0 | 283 | 2 | 1 | 0 | 29.9589333 | 12.0164 |
S2 | 0 | 0 | 0 | 0 | 2 | 1 | 0 | 30.0000000 | 12.0000 |
R2 | 0 | 1 | 0 | 257 | 2 | 1 | 0 | 30.0410667 | 11.9836 |
K2 | 0 | 2 | 0 | 0 | 2 | K2 | K2 | 30.0821373 | 11.9672 |
MSN2 | 1 | 0 | -1 | 0 | 2 | M2^2 | 2*M2 | 30.5443747 | 11.7861 |
KJ2 | 1 | 2 | -1 | 180 | 2 | K1*J1 | K1+J1 | 30.6265120 | 11.7545 |
2SM2 | 2 | -2 | 0 | 0 | 2 | M2 | -M2 | 31.0158958 | 11.6070 |
MO3 | -4 | 3 | 0 | 270 | 3 | M2*O1 | M2+O1 | 42.9271398 | 8.3863 |
M3 | -3 | 3 | 0 | 180 | 3 | M2^1.5 | 1.5*M2 | 43.4761563 | 8.2804 |
SO3 | -2 | 1 | 0 | 270 | 3 | O1 | O1 | 43.9430356 | 8.1924 |
MK3 | -2 | 3 | 0 | 90 | 3 | M2*K1 | M2+K1 | 44.0251729 | 8.1771 |
SK3 | 0 | 1 | 0 | 90 | 3 | K1 | K1 | 45.0410686 | 7.9927 |
MN4 | -5 | 4 | 1 | 0 | 4 | M2^2 | 2*M2 | 57.4238337 | 6.2692 |
M4 | -4 | 4 | 0 | 0 | 4 | M2^2 | 2*M2 | 57.9682084 | 6.2103 |
SN4 | -3 | 2 | 1 | 0 | 4 | M2 | M2 | 58.4397295 | 6.1602 |
MS4 | -2 | 2 | 0 | 0 | 4 | M2 | M2 | 58.9841042 | 6.1033 |
MK4 | -2 | 4 | 0 | 0 | 4 | M2*K2 | M2+K2 | 59.0662415 | 6.0949 |
S4 | 0 | 0 | 0 | 0 | 4 | 1 | 0 | 60.0000000 | 6.0000 |
SK4 | 0 | 2 | 0 | 0 | 4 | K2 | K2 | 60.0821373 | 5.9918 |
2MN6 | -7 | 6 | 1 | 0 | 6 | M2^3 | 3*M2 | 86.4079380 | 4.1663 |
M6 | -6 | 6 | 0 | 0 | 6 | M2^3 | 3*M2 | 86.9523127 | 4.1402 |
MSN6 | -5 | 4 | 1 | 0 | 6 | M2^2 | 2*M2 | 87.4238337 | 4.1179 |
2MS6 | -4 | 4 | 0 | 0 | 6 | M2^2 | 2*M2 | 87.9682084 | 4.0924 |
2MK6 | -4 | 6 | 0 | 0 | 6 | M2^2*K2 | 2*M2+K2 | 88.0503457 | 4.0886 |
2SM6 | -2 | 2 | 0 | 0 | 6 | M2 | M2 | 88.9841042 | 4.0457 |
MSK6 | -2 | 4 | 0 | 0 | 6 | M2*K2 | M2+K2 | 89.0662415 | 4.0419 |
Zi、Hi、ui の入手



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

これらのコンテンツを自動的に読み込んでゆき、各地点の分潮一覧表を ZIP 圧縮ファイルに格納していくプログラムが "makeTideDataFiles.php" である。
ある日の干潮、満潮時刻と潮位を求める
干潮、満潮は、1 日に各々最大 4 回起きる。1 分ごとに潮位を計算し、上昇から下降へ転じる点が満潮時刻、その逆が干潮時刻である。だが、1 分ごとに計算すると、1 日で 60×24=1440 回も計算しなければならない。
計算回数を減らすために、まず 15 分ごとに計算することで変化点を検出し、そこから±15 分間を 1 分ごとに計算することで正確な時刻を求める。計算回数は、1 日で 4×24+15×8
=216 回に減らすことができる。
1897: /**
1898: * 指定地点のある日の干潮、満潮時刻と潮位を求める
1899: * @param string $code 地点記号
1900: * @param int $year, $month, $day グレゴリオ暦による年月日
1901: * @param array $items 結果を格納する配列
1902: * array('high'=>array('hh:mm',潮位), 'low'=>array('hh:mm',潮位))
1903: * @return TRUE/FALSE
1904: */
1905: function tide_day($code, $year, $month, $day, &$items) {
1906: $this->setLocation($code);
1907: if ($this->error) return FALSE;
1908:
1909: $this->sun_moon($year, $month, $day);
1910:
1911: $interval = 15; //第1段階刻み(分)
1912: $td0 = $this->tide_level($year, $month, $day, self::JST, -$interval);
1913: $flag = 0;
1914: $cnt_high = 0;
1915: $cnt_low = 0;
1916: for ($i = 0; $i <= 24 * 60; $i += $interval) {
1917: //第1段階刻み
1918: $hh = floor($i / 60);
1919: $mm = $i - $hh * 60;;
1920: $td1 = $this->tide_level($year, $month, $day, $hh + self::JST, $mm);
1921: if ($flag == 0) {
1922: if ($td1 > $td0) $flag = +1; //上昇
1923: else $flag = -1; //下降
1924: } else if ($flag < 0) {
1925: if ($td1 > $td0) { //上昇へ転じた
1926: //第2段階刻み
1927: $td0 = $this->tide_level($year, $month, $day, $hh + self::JST, $mm - $interval - 1);
1928: for ($j = -$interval; $j < $interval; $j++) {
1929: $hh = floor(($i + $j) / 60);
1930: $mm = ($i + $j) - $hh * 60;;
1931: $td1 = $this->tide_level($year, $month, $day, $hh + self::JST, $mm);
1932: if ($td1 > $td0) { //上昇へ転じた
1933: $items['low'][$cnt_low]['hhmm'] = sprintf("%02d:%02d", $hh, $mm);
1934: $items['low'][$cnt_low]['lev'] = (int)$td0;
1935: $cnt_low++;
1936: $flag = +1;
1937: break;
1938: }
1939: $td0 = $td1;
1940: }
1941: }
1942: } else {
1943: if ($td1 < $td0) { //下降へ転じた
1944: //第2段階刻み
1945: $td0 = $this->tide_level($year, $month, $day, self::JST, $mm - $interval - 1);
1946: for ($j = -$interval; $j < $interval; $j++) {
1947: $hh = floor(($i + $j) / 60);
1948: $mm = ($i + $j) - $hh * 60;;
1949: $td1 = $this->tide_level($year, $month, $day, $hh + self::JST, $mm);
1950: if ($td1 < $td0) { //下降へ転じた
1951: $items['high'][$cnt_high]['hhmm'] = sprintf("%02d:%02d", $hh, $mm);
1952: $items['high'][$cnt_high]['lev'] = (int)$td0;
1953: $cnt_high++;
1954: $flag = -1;
1955: break;
1956: }
1957: $td0 = $td1;
1958: }
1959: }
1960: }
1961: $td0 = $td1;
1962: }
1963: return TRUE;
1964: }
解説:各種定数
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: //リファラ・チェック(直リン防止用;空文字ならチェックしない)
0028: define('REFER_ON', 'www.pahoo.org');
0029: //define('REFER_ON', '');
0030:
0031: //リリース・フラグ(公開時にはTRUEにすること)
0032: define('FLAG_RELEASE', FALSE);
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');
0045:
0046: //観測地点の初期値
0047: define('LOCATION_DEF', 'TK'); //東京
0048: define('LONGITUDE_DEF', 139.767);
0049: define('LATITUDE_DEF', 35.65);
0050:
0051: //日本標準時(世界時との差異時間)
0052: define('JST', +9);
0053:
0054: //月齢計算時刻
0055: define('MOONAGE_HOUR', 21);
0056:
0057: //Googleマップ関係
0058: define('GMAPID', 'gmap_id'); //マップID
0059: define('GOOGLE_MAPS_WIDTH', 800); //表示幅
0060: define('GOOGLE_MAPS_HEIGHT', 300); //表示高
0061: define('GMAP_DEFTYPE', 'roadmap'); //デフォルトのマップタイプ
0062: define('GMAP_DEFZOOM', 8); //デフォルトの拡大率
ただし、冒頭に述べたとおり計算量が大きいので、INTERVAL_DEF や INTERVAL_GRAPH_DEF は、あまり大きな値にしない方がいいだろう。

潮位グラフを描くために、jQuery プラグイン「jqPlot」を利用する。使い方については、「PHP で NHK 政治意識月例調査をグラフ表示」を参照のこと。定数 JQPLOT の示すディレクトリに配置する。
また、月齢を示す月の画像は、定数 MOONAGE の示すディレクトリに配置する。圧縮ファイルに含まれている画像は、「PHP で月の満ち欠けを描画」で作成したモノだ。
解説:地点セレクタ
0336: /**
0337: * 地点セレクタを作成する
0338: * @param pahooTide $pt 潮位計算クラス
0339: * @param string $code 地点記号
0340: * @param string $errmsg エラーメッセージ格納用;エラーなければ空文字
0341: * @return string HTML/FALSE:失敗
0342: */
0343: function makeJSlocation($pt, $code) {
0344: static $prefs = array(
0345: '--選択--','北海道', '青森県', '岩手県', '宮城県', '秋田県', '山形県', '福島県',
0346: '茨城県', '栃木県', '群馬県', '埼玉県', '千葉県', '東京都', '神奈川県',
0347: '新潟県', '富山県', '石川県', '福井県', '山梨県', '長野県', '岐阜県', '静岡県',
0348: '愛知県', '三重県', '滋賀県', '京都府', '大阪府', '兵庫県', '奈良県',
0349: '和歌山県', '鳥取県', '島根県', '岡山県', '広島県', '山口県', '徳島県',
0350: '香川県', '愛媛県', '高知県', '福岡県', '佐賀県', '長崎県', '熊本県', '大分県',
0351: '宮崎県', '鹿児島県', '沖縄県');
0352:
0353: if ($code == '') {
0354: $pp = '';
0355: } else if (! isset($pt->index[$code])) {
0356: $errmsg = "not exist location '{$code}'";
0357: return FALSE;
0358: } else {
0359: $pp = $pt->index[$code][4];
0360: }
0361:
0362: //都道府県
0363: $js = "var pref = [\n";
0364: foreach ($prefs as $key=>$val) {
0365: $selected = ($val == $pp) ? 'selected' : '';
0366: $js .= "{'type':{$key}, 'text':'{$val}', 'value':'{$val}', 'selected':'{$selected}'},\n";
0367: }
0368: //地点
0369: $js .= "];\nvar loc = [\n";
0370: foreach ($prefs as $key=>$val) {
0371: $js .= "{'type':{$key}, 'text':'--選択--', 'value':''},\n";
0372: foreach ($pt->index as $cd=>$arr) {
0373: if ($arr[4] == $val) {
0374: $selected = ($cd == $code) ? 'selected' : '';
0375: $js .= "{'type':{$key}, 'text':'{$arr[0]}', 'value':'{$cd}', 'selected':'{$selected}'},\n";
0376: }
0377: }
0378: }
0379:
0380: //スクリプト
0381: $js .=<<< EOD
0382: ];
0383:
0384: $(function() {
0385: var html = '';
0386: for (var i in pref) {
0387: html += '<option value="'+ pref[i].value + '" ' + pref[i].selected + '>'+ pref[i].text +'</option>';
0388: }
0389: $('#pref').append(html);
0390:
0391: var html = '';
0392: var index = $('#pref option:selected').index();
0393: for (var i in loc) {
0394: if (loc[i].type == index) {
0395: html += '<option value="'+ loc[i].value + '" ' + loc[i].selected + '>' + loc[i].text +'</option>';
0396: }
0397: }
0398: $('#code').append(html);
0399:
0400: //都道府県を選択した時の挙動
0401: $('#pref').change(function() {
0402: var html = '';
0403: var index = $('#pref option:selected').index();
0404: //地点の内容を削除
0405: $('#code').empty();
0406: //対応する地点の内容を表示
0407: for (var i in loc) {
0408: if (loc[i].type == index) {
0409: html += '<option value="'+ loc[i].value +'">'+ loc[i].text +'</option>';
0410: }
0411: }
0412: $('#code').append(html);
0413: });
0414: });
0415:
0416: EOD;
0417: return $js;
0418: }
観測地点セレクタを都道府県セレクタに連動させるために、jQuery を利用した。
解説:潮位表の計算と作成
0421: /**
0422: * 潮位表:潮位・月齢を計算する
0423: * @param pahooTide $pt 潮位計算クラス
0424: * @param pahooCalendar $pc 暦計算クラス
0425: * @param array $inputs 計算用パラメータ
0426: * @param array $items 計算結果格納用配列
0427: * @param array $locs 地点情報格納用配列
0428: * @param string $errmsg エラーメッセージ格納用;エラーなければ空文字
0429: * @return bool TRUE:計算成功/FALSE:失敗
0430: */
0431: function calcTideTable($pt, $pc, $params, &$items, &$locs, &$errmsg) {
0432: //地点を設定
0433: $pt->setLocation($params['code']);
0434: if ($pt->iserror()) return FALSE;
0435:
0436: //地点を取得
0437: $pt->getLocation($params['code'], $locs);
0438: if ($pt->iserror()) return FALSE;
0439:
0440: //月齢・潮位を計算
0441: $res = TRUE;
0442: $jd = $pc->Gregorian2JD($params['year'], $params['month'], $params['day'], 0, 0, 0);
0443: for ($i = 0; $i < $params['interval']; $i++) {
0444: $arr = array();
0445: list($year, $month, $day, $hour, $min, $sec) = $pc->JD2Gregorian($jd);
0446: $yb = $pc->getWeekString($year, $month, $day);
0447: $items[$i]['dt'] = sprintf("%04d-%02d-%02d(%s)", $year, $month, $day, $yb);
0448: $items[$i]['moonage'] = $pc->moon_age($year, $month, $day, MOONAGE_HOUR, 0, 0);
0449: $moonmeridian = $pc->moon_time(2, $params['longitude'], $params['latitude'], $params['height'], $year, $month, $day);
0450: $moonmeridian = ($moonmeridian == FALSE) ? '---' : $pc->day2hhmm($moonmeridian);
0451: $items[$i]['tide'] = (preg_match('/([0-9]+)\:([0-9]+)/', $moonmeridian, $arr) > 0) ? $pc->tide($year, $month, $day, $arr[1], $arr[2], 0) : '';
0452: unset($arr);
0453:
0454: $arr = array();
0455: $pt->tide_day($params['code'], $year, $month, $day, $arr);
0456: //満潮
0457: for ($j = 0; $j < 4; $j++) {
0458: if (isset($arr['high'][$j])) {
0459: $items[$i]['high'][$j]['hhmm'] = $arr['high'][$j]['hhmm'];
0460: $items[$i]['high'][$j]['lev'] = sprintf("%d", $arr['high'][$j]['lev']);
0461: $items[$i]['high'][$j]['align'] = 'text-align:right;';
0462: } else {
0463: $items[$i]['high'][$j]['hhmm'] = '*';
0464: $items[$i]['high'][$j]['lev'] = '*';
0465: $items[$i]['high'][$j]['align'] = 'text-align:center;';
0466: }
0467: }
0468: //干潮
0469: for ($j = 0; $j < 4; $j++) {
0470: if (isset($arr['low'][$j])) {
0471: $items[$i]['low'][$j]['hhmm'] = $arr['low'][$j]['hhmm'];
0472: $items[$i]['low'][$j]['lev'] = sprintf("%d", $arr['low'][$j]['lev']);
0473: $items[$i]['low'][$j]['align'] = 'text-align:right;';
0474: } else {
0475: $items[$i]['low'][$j]['hhmm'] = '*';
0476: $items[$i]['low'][$j]['lev'] = '*';
0477: $items[$i]['low'][$j]['align'] = 'text-align:center;';
0478: }
0479: }
0480: $jd++;
0481: }
0482:
0483: return $res;
0484: }
月齢・潮の計算は、「PHP で日出没・月出没・月齢・潮を計算」で紹介したとおりである。
0486: /**
0487: * 潮位表:表示用HTMLを作成
0488: * @param pahooTide $pt 潮位計算クラス
0489: * @param pahooCalendar $pc 暦計算クラス
0490: * @param array $params 計算用パラメータ
0491: * @param string $js スクリプト格納用
0492: * @param string $html HTML格納用
0493: * @param string $errmsg エラーメッセージ格納用
0494: * @return bool TRUE:成功/FALSE:失敗
0495: * @return 表示用HTML
0496: */
0497: function makeTable($pt, $pc, $params, &$js, &$html, &$errmsg) {
0498: $js = $html = '';
0499: $items = array();
0500: $locs = array();
0501: if (calcTideTable($pt, $pc, $params, $items, $locs, $errmsg) == FALSE) return FALSE;
0502:
0503: $hour = MOONAGE_HOUR;
0504: $html =<<< EOD
0505: <table class="tide">
0506: <caption>{$locs['title']}({$locs['prefecture']}{$locs['address']})</caption>
0507: <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>
0508:
0509: EOD;
0510: $html .= "<tr>";
0511: for ($j = 0; $j < 8; $j++) {
0512: $html .= "<th class=\"index\">時刻</th><th class=\"index\">潮位<br />(cm)</th>";
0513: }
0514: $html .= "</tr>\n";
0515: foreach ($items as $item) {
0516: $html .= sprintf("<tr><td>%s</td><td>%.1f</td><td>%s</td>", $item['dt'], $item['moonage'], $item['tide']);
0517: for ($j = 0; $j < 4; $j++) {
0518: $html .= sprintf("<td>%s</td><td style=\"%s\">%s</td>", $item['high'][$j]['hhmm'], $item['high'][$j]['align'], $item['high'][$j]['lev']);
0519: }
0520: for ($j = 0; $j < 4; $j++) {
0521: $html .= sprintf("<td>%s</td><td style=\"%s\">%s</td>", $item['low'][$j]['hhmm'], $item['low'][$j]['align'], $item['low'][$j]['lev']);
0522: }
0523: $html .= "</tr>\n";
0524: }
0525: $html .= "</table>\n";
0526:
0527: return TRUE;
0528: }
解説:潮位グラフの計算と作成
0530: /**
0531: * 潮位グラフ:潮位・月齢を計算する
0532: * @param pahooTide $pt 潮位計算クラス
0533: * @param pahooCalendar $pc 暦計算クラス
0534: * @param array $params 計算用パラメータ
0535: * @param array $items 潮位計算結果格納用配列
0536: * @param array $moons 月齢計算結果格納用配列
0537: * @param array $locs 地点情報格納用配列
0538: * @param string $errmsg エラーメッセージ格納用;エラーなければ空文字
0539: * @return bool TRUE:計算成功/FALSE:失敗
0540: */
0541: function calcTideGraph($pt, $pc, $params, &$items, &$moons, &$locs, &$errmsg) {
0542: //地点を設定
0543: $pt->setLocation($params['code']);
0544: if ($pt->iserror()) return FALSE;
0545:
0546: //地点を取得
0547: $pt->getLocation($params['code'], $locs);
0548: if ($pt->iserror()) return FALSE;
0549:
0550: $res = TRUE;
0551:
0552: //潮位を計算
0553: $jd = $pc->Gregorian2JD($params['year'], $params['month'], $params['day'], -JST, 0, 0);
0554: $n = $params['interval'] / INTERVAL_GRAPH;
0555: for ($i = 0; $i < $n; $i++) {
0556: list($year, $month, $day, $hour, $min, $sec) = $pc->JD2Gregorian($jd + JST / 24);
0557: $items[$i]['dt'] = sprintf("%04d/%02d/%02d %02d:%02d", $year, $month, $day, $hour, $min);
0558: list($year, $month, $day, $hour, $min, $sec) = $pc->JD2Gregorian($jd);
0559: $pt->sun_moon($year, $month, $day);
0560: $items[$i]['tlevel'] = $pt->tide_level($year, $month, $day, $hour, $min);
0561: $jd += INTERVAL_GRAPH;
0562: }
0563:
0564: //月齢を計算
0565: $height = 0; //標高0メートルを仮定
0566: $n = $params['interval'];
0567: $jd = $pc->Gregorian2JD($params['year'], $params['month'], $params['day'], 0, 0, 0);
0568: $i = $d = 0;
0569: while ($i < $n) {
0570: //月の南中時刻
0571: list($year, $month, $day, $hour, $min, $sec) = $pc->JD2Gregorian($jd);
0572: $moonmeridian = $pc->moon_time(2, $params['longitude'], $params['latitude'], $height, $year, $month, $day);
0573: if ($moonmeridian != FALSE) {
0574: $x = (double)$i + $moonmeridian;
0575: $moons[$i]['t'] = $d + $moonmeridian;
0576: $moons[$i]['age'] = $pc->moon_age($year, $month, $day, $moonmeridian * 24, 0, 0);
0577: $i++;
0578: }
0579: $jd++;
0580: $d++;
0581: }
0582:
0583: return $res;
0584: }

月齢は、月の画像をグラフ上にオーバーライトすることを目指す。プロットする場所は、月の南中時刻とすべく、メソッド pahooCalendar::moon_time によって南中時刻を求める。
0586: /**
0587: * 潮位グラフ:表示用スクリプトおよびHTMLを作成
0588: * @param pahooTide $pt 潮位計算クラス
0589: * @param pahooCalendar $pc 暦計算クラス
0590: * @param array $params 計算用パラメータ
0591: * @param string $js スクリプト格納用
0592: * @param string $html HTML格納用
0593: * @param string $errmsg エラーメッセージ格納用
0594: * @return bool TRUE:成功/FALSE:失敗
0595: */
0596: function makeGraph($pt, $pc, $params, &$js, &$html, &$errmsg) {
0597: $js = $html = '';
0598: $items = array();
0599: $moons = array();
0600: $locs = array();
0601: if (calcTideGraph($pt, $pc, $params, $items, $moons, $locs, $errmsg) == FALSE) return FALSE;
0602:
0603: $title = "{$locs['title']}({$locs['prefecture']}{$locs['address']})";
0604: $name = GRAPH_TIDE;
0605: $width = GOOGLE_MAPS_WIDTH;
0606: $height = GOOGLE_MAPS_HEIGHT;
0607:
0608: //潮位プロット・データ作成
0609: $data = '';
0610: foreach ($items as $item) {
0611: $data .= sprintf("['%s', %f], ", $item['dt'], $item['tlevel']);
0612: }
0613:
0614: //月齢データ作成
0615: $moon = '';
0616: $path = MOONAGE;
0617: foreach ($moons as $val) {
0618: $x = round(30 + (GOOGLE_MAPS_WIDTH - 70) / $params['interval'] * $val['t']);
0619: $fname = sprintf("{$path}moon_%02d.png", round($val['age']));
0620: $moon .=<<< EOD
0621: <img style="position:absolute; top:35px; left:{$x}px; width:40px; height:40px; z-index:999;" src="{$fname}" />
0622:
0623: EOD;
0624: }
0625:
0626: $js =<<< EOD
0627: $(function() {
0628: jQuery.jqplot('{$name}',
0629: [
0630: [ {$data} ]
0631: ],
0632: {
0633: //タイトル
0634: title: {
0635: text: '{$title}',
0636: show: true,
0637: fontSize: '16px',
0638: textAlign: 'center',
0639: textColor: 'black'
0640: },
0641: //背景
0642: grid: {
0643: background: '#EEFFFF'
0644: },
0645: //グラフ
0646: seriesDefaults: {
0647: showLine: true,
0648: rendererOptions: { smooth: false },
0649: markerOptions: { size: 0 },
0650: color: 'blue',
0651: },
0652: //軸ラベル
0653: axes: {
0654: xaxis: {
0655: renderer: $.jqplot.DateAxisRenderer,
0656: tickOptions: { formatString: '%m/%d' },
0657: tickInterval: '1 days'
0658: },
0659: yaxis: {
0660: label: '潮位(cm)'
0661: }
0662: }
0663: }
0664: );
0665: });
0666:
0667: EOD;
0668: $html =<<< EOD
0669: <div id="{$name}" style="width:{$width}px; height:{$height}px;">
0670: {$moon}
0671: </div>
0672:
0673: EOD;
0674:
0675: return TRUE;
jqPlot に渡すスクリプトと、表示する HTML は別々に生成する。jqPlot で画像を扱えないため、無理をして HTML 側でグラフ上にオーバーライトしている。
解説:メインプログラム
Google マップの表示や住所検索については「PHP で Google等を利用して住所から緯度・経度を求める」を、UI:Datepicker を利用した日付入力については「PHP で日付入力:カレンダーから選択」を参照してほしい。
参考サイト
- PHP で日出没・月出没・月齢・潮を計算:ぱふぅ家のホームページ
- PHP で NHK 政治意識月例調査をグラフ表示:ぱふぅ家のホームページ
- PHP で月の満ち欠けを描画:ぱふぅ家のホームページ
- 潮位表掲載地点一覧表:気象庁
さて、「PHP で日出没・月出没・月齢・潮を計算」では潮の計算を行うプログラムを紹介したが、潮位を計算するには、はるかに複雑な計算を行う必要がある。幸い、コンピュータの計算能力が飛躍的に向上したおかげで、潮位の計算もストレスなく行えるようになった。
そこで、これまで作ってきた "pahooCalendar.php" に、あらた潮位計算クラス "pahooTide" を追加し、Google マップに表示されている地点の近くの海岸において、指定日から 1週間の毎日の満潮・干潮の時刻と潮位を一覧およびグラフ表示するサンプル・プログラムを作ってみることにする。
(2019 年 2 月 9 日)pahooCalendar::makeLunarCalendar の不具合を修正、getSolarTerm72 の漢字表記を変更した。