1. システム概要と動作構造
-
システム名: 天体運行案内Webアプリケーション
-
設置環境: エックスサーバー(Xserver) / PHP 7.4以上
-
配置ディレクトリ:
/home/tanzawatake/radish-plan.com/public_html/sun/ -
動作仕様:
-
太陽の日の出・日の入り時刻(指定6都市の緯度・経度より自動計算)を表示。
-
月の状況(21時固定の国立天文台データ):月相SVG、月名、公表月齢、視半径、方位、高度、スーパームーン判定を表示。
-
5大惑星の状況(18時/21時/00時/03時切り替え):選択時刻に応じた高度・方位・等級・視半径を表示。
-
00時および03時選択時は、天文学的な夜の連続性を保つため「翌日の日付データ」を自動参照する仕様。
-
2. 必要なファイル一覧と配置場所
Webサーバー上の該当ディレクトリ(public_html/sun/)へ、以下の計6ファイルを配置します。
| ファイル名 | 役割 | フォーマット仕様 |
index.php |
メインプログラム(HTML・画面描画・データ解析) | UTF-8 (BOMなし) |
moon_2026_21h.csv |
21時基準の月データ(国立天文台出力順) | CSV (1行目ヘッダー) |
planets_2026_18h.csv |
18時(夕方)の惑星データ | CSV (1行目ヘッダー) |
planets_2026_21h.csv |
21時(宵の内)の惑星データ | CSV (1行目ヘッダー) |
planets_2026_00h.csv |
00時(真夜中)の惑星データ | CSV (1行目ヘッダー) |
planets_2026_03h.csv |
03時(未明)の惑星データ | CSV (1行目ヘッダー) |
3. CSVデータファイルのフォーマット規定
プログラムが読み込むCSVファイル(国立天文台の各種データを整形したもの)の列構造およびヘッダーは、以下に厳密に統一します。
① 月データCSV(moon_2026_21h.csv)
コード スニペット
年月日,時刻,高度[°],方位[°],視半径[″],月齢,天体
2026/1/1,21:00:00,77,124.6,1011.6,12.4,月
2026/1/2,21:00:00,65,99,1009.9,13.4,月
-
1列目(0): 年月日 (
YYYY/M/DまたはYYYY-MM-DD) -
3列目(2): 高度[°]
-
4列目(3): 方位[°]
-
5列目(4): 視半径[″]
-
6列目(5): 月齢
② 惑星データCSV(planets_2026_18h.csv 〜 03h.csv)
コード スニペット
年月日,時刻,高度[°],方位[°],視半径[″],等級,天体
2026/1/1,18:00:00,-26.3,258.1,2.4,-0.6,水星
2026/1/1,18:00:00,-17,252.8,4.9,-3.9,金星
2026/1/1,18:00:00,-14.3,250.9,1.9,1.1,火星
2026/1/1,18:00:00,6.7,67.6,23.2,-2.7,木星
2026/1/1,18:00:00,47.6,207,8.5,1,土星
-
1列目(0): 年月日 (
YYYY/M/DまたはYYYY-MM-DD) -
3列目(2): 高度[°]
-
4列目(3): 方位[°]
-
5列目(4): 視半径[″]
-
6列目(5): 等級
-
7列目(6): 天体名(水星・金星・火星・木星・土星)
4. プログラム全コード(index.php)
以下が、文字コード(Shift_JIS/UTF-8)自動変換、構文エラー修正、時間切り替え、月データの高度・方位表示追加、日付またぎ補正をすべて反映した完成版プログラムです。
PHP
<?php
date_default_timezone_set('Asia/Tokyo');
// 1. 6都市の緯度・経度リスト
$places = [
'秦野市' => ['lat' => 35.3722, 'lng' => 139.2272],
'三島市' => ['lat' => 35.1184, 'lng' => 138.9185],
'甲府市' => ['lat' => 35.6621, 'lng' => 138.5683],
'松本市' => ['lat' => 36.2380, 'lng' => 137.9720],
'盛岡市' => ['lat' => 39.7036, 'lng' => 141.1527],
'弘前市' => ['lat' => 40.6031, 'lng' => 140.4642],
];
$selected_place = '秦野市';
if (isset($_POST['place']) && array_key_exists($_POST['place'], $places)) {
$selected_place = $_POST['place'];
}
// 選択可能な4つの時間帯の定義(惑星用)
$time_options = [
'18' => ['label' => '18:00 (夕方)'],
'21' => ['label' => '21:00 (宵の内)'],
'00' => ['label' => '00:00 (真夜中)'],
'03' => ['label' => '03:00 (未明)'],
];
$selected_time = '21'; // デフォルトは21時
if (isset($_POST['time']) && array_key_exists($_POST['time'], $time_options)) {
$selected_time = $_POST['time'];
}
$lat = $places[$selected_place]['lat'];
$lng = $places[$selected_place]['lng'];
$today = time();
$date_str = date('Y-m-d', $today);
// ☀️ 太陽の計算
$sunrise = date_sunrise($today, SUNFUNCS_RET_STRING, $lat, $lng, 90.5833, 9);
$sunset = date_sunset($today, SUNFUNCS_RET_STRING, $lat, $lng, 90.5833, 9);
// ===================================================
// 🌙 21時固定:正確な月データを読み込む
// ===================================================
$precise_moon_age = 0.0;
$moon_size = 0.0;
$moon_alt = 0.0;
$moon_az = 0.0;
$moon_csv = "moon_2026_21h.csv"; // 常に21時固定
if (file_exists($moon_csv)) {
// 文字化け・文字コード対策のためUTF-8に一括変換して読み込み
$moon_data_raw = file_get_contents($moon_csv);
$moon_data_utf8 = mb_convert_encoding($moon_data_raw, 'UTF-8', 'ASCII,JIS,UTF-8,EUC-JP,SJIS-WIN,eucJP-win');
$handle = fopen('php://temp', 'r+');
fwrite($handle, $moon_data_utf8);
rewind($handle);
$headers = fgetcsv($handle, 1000, ",");
while (($row = fgetcsv($handle, 1000, ",")) !== FALSE) {
if (count($row) >= 7) {
$csv_date = date('Y-m-d', strtotime($row[0]));
if ($csv_date === $date_str) {
$moon_alt = (float)$row[2]; // 高度 (3列目)
$moon_az = (float)$row[3]; // 方位 (4列目)
$moon_size = (float)$row[4]; // 視半径 (5列目)
$precise_moon_age = (float)$row[5]; // 月齢 (6列目)
break;
}
}
}
fclose($handle);
}
// 描画や名前決定に使用する月齢
$moon_age = (int)round($precise_moon_age) % 30;
// 月の名前決定ロジック
if ($moon_age >= 0 && $moon_age < 2) { $moon_name = '新月(しんげつ)'; }
elseif ($moon_age >= 2 && $moon_age < 3) { $moon_name = '二日月(ふつかづき)'; }
elseif ($moon_age >= 3 && $moon_age < 4) { $moon_name = '三日月(みかづき)'; }
elseif ($moon_age >= 4 && $moon_age < 7) { $moon_name = '名無しの月'; }
elseif ($moon_age >= 7 && $moon_age < 8) { $moon_name = '上弦の月(じょうげんのつき)'; }
elseif ($moon_age >= 8 && $moon_age < 9) { $moon_name = '弓張月(ゆみはりづき)'; }
elseif ($moon_age >= 9 && $moon_age < 10) { $moon_name = '名無しの月'; }
elseif ($moon_age >= 10 && $moon_age < 11) { $moon_name = '十日余りの月(とおかあまりのつき)'; }
elseif ($moon_age >= 11 && $moon_age < 13) { $moon_name = '名無しの月'; }
elseif ($moon_age >= 13 && $moon_age < 14) { $moon_name = '十三夜月(じゅうさんやづき)'; }
elseif ($moon_age >= 14 && $moon_age < 15) { $moon_name = '小望月(こもちづき)'; }
elseif ($moon_age >= 15 && $moon_age < 16) { $moon_name = '十五夜(じゅうごや)、満月'; }
elseif ($moon_age >= 16 && $moon_age < 17) { $moon_name = '十六夜(いざよい)'; }
elseif ($moon_age >= 17 && $moon_age < 18) { $moon_name = '立待月(たちまちづき)'; }
elseif ($moon_age >= 18 && $moon_age < 19) { $moon_name = '居待月(いまちづき)'; }
elseif ($moon_age >= 19 && $moon_age < 20) { $moon_name = '寝待月(ねまちづき)'; }
elseif ($moon_age >= 20 && $moon_age < 21) { $moon_name = '更待月(ふけまちづき)'; }
elseif ($moon_age >= 22 && $moon_age < 24) { $moon_name = '下弦(かげん)'; }
elseif ($moon_age >= 26 && $moon_age < 27) { $moon_name = '有明の月(ありあけのつき)'; }
else { $moon_name = '名無しの月'; }
// スーパームーン判定ロジック
if ($precise_moon_age >= 14.0 && $precise_moon_age < 17.0 && $moon_size >= 1000.0) {
$moon_name .= ' <span style="color: #ff4500; background: #fff0f0; padding: 2px 8px; border-radius: 4px; font-size: 0.9rem; margin-left: 8px; border: 1px solid #ffccc7;">スーパームーン</span>';
}
// 月相描画関数
function render_moon_svg($age) {
$age = $age % 30;
$svg_start = '<svg width="100" height="100" viewBox="0 0 100 100" style="background:#111; border-radius:50%;">';
$svg_end = '</svg>';
if ($age == 0) return $svg_start . '<circle cx="50" cy="50" r="50" fill="#333" />' . $svg_end;
if ($age == 15) return $svg_start . '<circle cx="50" cy="50" r="50" fill="#FFD700" />' . $svg_end;
$rad = ($age / 30) * 2 * M_PI;
$rx = abs(50 * cos($rad));
if ($age < 15) {
if ($age < 7.5) {
$light = '<path d="M 50 0 A 50 50 0 0 1 50 100 Z" fill="#FFD700" />';
$shadow = '<path d="M 50 0 A ' . $rx . ' 50 0 0 1 50 100 Z" fill="#333" />';
return $svg_start . '<circle cx="50" cy="50" r="50" fill="#333" />' . $light . $shadow . $svg_end;
} else {
$light = '<path d="M 50 0 A 50 50 0 0 1 50 100 A ' . $rx . ' 50 0 0 1 50 0 Z" fill="#FFD700" />';
return $svg_start . '<circle cx="50" cy="50" r="50" fill="#333" />' . $light . $svg_end;
}
} else {
if ($age < 22.5) {
$light = '<path d="M 50 0 A ' . $rx . ' 50 0 0 1 50 100 A 50 50 0 0 1 50 0 Z" fill="#FFD700" />';
return $svg_start . '<circle cx="50" cy="50" r="50" fill="#333" />' . $light . $svg_end;
} else {
$light = '<path d="M 50 0 A 50 50 0 0 0 50 100 Z" fill="#FFD700" />';
$shadow = '<path d="M 50 0 A ' . $rx . ' 50 0 0 0 50 100 Z" fill="#333" />';
return $svg_start . '<circle cx="50" cy="50" r="50" fill="#333" />' . $light . $shadow . $svg_end;
}
}
}
// 16方位変換関数
function azimuth_to_16方位($az) {
$方位リスト = ['北', '北北東', '北東', '東北東', '東', '東南東', '南東', '南南東', '南', '南南西', '南西', '西南西', '西', '西北西', '北西', '北北西', '北'];
$index = (int)round($az / 22.5) % 16;
return $方位リスト[$index];
}
// 高度目安テキスト関数
function get_altitude_text($alt) {
if ($alt < 0) return '地平線の下(不視)';
if ($alt >= 80) return '頭上真上付近(天頂)';
if ($alt >= 45) return '高く見上げる位置';
if ($alt >= 15) return '見やすい高さ(中空)';
return '地平線に近い(低空)';
}
// 星の輝きSVG生成関数
function render_star_svg($name, $mag, $alt) {
$r = 10 - ($mag * 2);
if ($r < 3) $r = 3;
if ($r > 14) $r = 14;
$star_color = '#FFFFFF';
if ($alt >= 0) {
switch ($name) {
case '水星': $star_color = '#CBD5E1'; break;
case '金星': $star_color = '#FFFEE0'; break;
case '火星': $star_color = '#FF5733'; break;
case '木星': $star_color = '#F3E5AB'; break;
case '土星': $star_color = '#E6C280'; break;
}
} else {
$star_color = '#555555';
}
$svg = '<svg width="50" height="50" viewBox="0 0 40 40" style="background:#111; border-radius:4px;">';
if ($alt >= 0 && $mag <= 0) {
$svg .= '<circle cx="20" cy="20" r="' . ($r * 1.8) . '" fill="' . $star_color . '" opacity="0.25" />';
$svg .= '<circle cx="20" cy="20" r="' . ($r * 1.3) . '" fill="' . $star_color . '" opacity="0.45" />';
}
$svg .= '<circle cx="20" cy="20" r="' . $r . '" fill="' . $star_color . '" />';
$svg .= '</svg>';
return $svg;
}
// 5大惑星の読み込みロジック(選択した時間に連動、文字コード自動変換付き)
$planet_data = [];
$csv_file = "planets_2026_{$selected_time}h.csv";
// 00時と03時は「翌日」のデータを指すため、日付文字列を1日進める
$planet_date_str = $date_str;
if ($selected_time === '00' || $selected_time === '03') {
$planet_date_str = date('Y-m-d', strtotime('+1 day', $today));
}
if (file_exists($csv_file)) {
$p_data_raw = file_get_contents($csv_file);
$p_data_utf8 = mb_convert_encoding($p_data_raw, 'UTF-8', 'ASCII,JIS,UTF-8,EUC-JP,SJIS-WIN,eucJP-win');
$handle = fopen('php://temp', 'r+');
fwrite($handle, $p_data_utf8);
rewind($handle);
$headers = fgetcsv($handle, 1000, ",");
while (($row = fgetcsv($handle, 1000, ",")) !== FALSE) {
if (count($row) >= 7) {
$csv_date = date('Y-m-d', strtotime($row[0]));
if ($csv_date === $planet_date_str) {
$planet_data[] = [
'name' => trim($row[6]), // 天体名 (7列目)
'mag' => (float)$row[5], // 等級 (6列目)
'size' => (float)$row[4], // 視半径 (5列目)
'alt' => (float)$row[2], // 高度 (3列目)
'az' => (float)$row[3] // 方位 (4列目)
];
}
}
}
fclose($handle);
}
?>
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>天体運行案内</title>
<style>
body { font-family: sans-serif; max-width: 650px; margin: 40px auto; padding: 0 20px; color: #333; line-height: 1.6; background-color: #fafafa; }
h1 { border-bottom: 2px solid #333; padding-bottom: 10px; font-size: 1.4rem; text-align: center; margin-bottom: 5px; }
.top-form { margin: 15px 0 25px 0; text-align: center; }
select.place-select { padding: 8px 12px; font-size: 1rem; border-radius: 4px; border: 1px solid #ccc; width: 100%; max-width: 200px; }
.date-display { text-align: center; font-size: 1rem; color: #666; margin-bottom: 20px; }
.container { display: flex; flex-direction: column; gap: 20px; }
.card { background: #ffffff; padding: 20px; border-radius: 8px; border: 1px solid #ddd; }
.card h2 { margin-top: 0; font-size: 1.1rem; border-left: 4px solid #333; padding-left: 10px; color: #111; }
.planet-header-flex { display: flex; justify-content: space-between; align-items: center; border-left: 4px solid #333; padding-left: 10px; margin-bottom: 15px; }
.planet-header-flex h2 { border-left: none; padding-left: 0; margin: 0; }
select.time-select { padding: 5px 10px; font-size: 0.9rem; border-radius: 4px; border: 1px solid #ccc; cursor: pointer; }
.coords { color: #777; font-size: 0.85rem; margin-bottom: 15px; }
.time-info { font-size: 1.2rem; font-weight: bold; margin: 12px 0; }
.moon-flex { display: flex; align-items: center; justify-content: center; gap: 30px; padding: 10px 0; }
.planet-table { width: 100%; border-collapse: collapse; margin-top: 10px; }
.planet-table th { background: #f0f0f0; padding: 10px; font-size: 0.85rem; text-align: left; border-bottom: 2px solid #ddd; }
.planet-table td { padding: 12px 10px; border-bottom: 1px solid #eee; font-size: 0.9rem; vertical-align: middle; }
.planet-invisible { color: #999; background-color: #fcfcfc; }
.alt-meter-container { display: inline-block; width: 60px; background: #e0e0e0; height: 8px; border-radius: 4px; vertical-align: middle; margin-right: 5px; overflow: hidden; }
.alt-meter-bar { background: #1e90ff; height: 100%; }
</style>
</head>
<body>
<h1><?php echo $selected_place; ?> 天体運行案内</h1>
<div class="date-display">本日:<?php echo date('Y年m月d日', $today); ?></div>
<div class="top-form">
<form id="placeForm" method="POST" action="">
<input type="hidden" name="time" value="<?php echo $selected_time; ?>">
<select name="place" class="place-select" onchange="document.getElementById('placeForm').submit()">
<?php foreach ($places as $name => $coords): ?>
<option value="<?php echo $name; ?>" <?php echo ($name === $selected_place) ? 'selected' : ''; ?>>
<?php echo $name; ?>
</option>
<?php endforeach; ?>
</select>
</form>
</div>
<div class="container">
<div class="card">
<h2>☀️ 日の出入り(市役所付近)</h2>
<p class="coords">観測地点:北緯 <?php echo $lat; ?>度 / 東経 <?php echo $lng; ?>度</p>
<div class="time-info">☀️ 日の出: <?php echo $sunrise; ?></div>
<div class="time-info">🌙 日の入り: <?php echo $sunset; ?></div>
</div>
<div class="card">
<h2>🌙 今夜の月相(21:00基準)</h2>
<?php if ($precise_moon_age == 0.0): ?>
<p style="color:#cc0000; font-size:0.9rem;">※ フォルダ内に「<?php echo $moon_csv; ?>」データがないか、本日のデータが登録されていません。</p>
<?php else: ?>
<div class="moon-flex">
<div><?php echo render_moon_svg($moon_age); ?></div>
<div>
<div style="font-size:1.2rem; font-weight:bold; display: flex; align-items: center;"><?php echo $moon_name; ?></div>
<div style="color:#666; font-size:0.9rem; margin-top:5px; line-height: 1.6;">
天文台公表月齢: <?php echo number_format($precise_moon_age, 1); ?><br>
見かけ寸法(視半径): <?php echo number_format($moon_size, 1); ?>″ (秒)<br>
見える方位: <?php echo ($moon_alt < 0) ? 'ー' : azimuth_to_16方位($moon_az) . ' (' . round($moon_az) . '°)'; ?><br>
高さ(高度): <span style="font-weight:bold; color:#111;"><?php echo round($moon_alt); ?>°</span> (<?php echo get_altitude_text($moon_alt); ?>)
</div>
</div>
</div>
<?php endif; ?>
</div>
<div class="card">
<div class="planet-header-flex">
<h2>🪐 今夜<?php echo $selected_time; ?>時の惑星状況</h2>
<form id="timeForm" method="POST" action="">
<input type="hidden" name="place" value="<?php echo $selected_place; ?>">
<select name="time" class="time-select" onchange="document.getElementById('timeForm').submit()">
<?php foreach ($time_options as $value => $info): ?>
<option value="<?php echo $value; ?>" <?php echo (strval($value) === strval($selected_time)) ? 'selected' : ''; ?>>
<?php echo $info['label']; ?>
</option>
<?php endforeach; ?>
</select>
</form>
</div>
<?php if (empty($planet_data)): ?>
<p style="color:#cc0000; font-size:0.9rem;">※ フォルダ内に「<?php echo $csv_file; ?>」データがないか、本日のデータが登録されていません。</p>
<?php else: ?>
<table class="planet-table">
<thead>
<tr>
<th>惑星</th>
<th>輝き</th>
<th>明るさ</th>
<th>見える方位</th>
<th>高さ(高度)</th>
</tr>
</thead>
<tbody>
<?php foreach ($planet_data as $p):
$isInvisible = ($p['alt'] < 0);
$trClass = $isInvisible ? 'class="planet-invisible"' : '';
$altPercent = $isInvisible ? 0 : ($p['alt'] / 90) * 100;
?>
<tr <?php echo $trClass; ?>>
<td style="font-weight:bold; color:#111;"><?php echo $p['name']; ?></td>
<td><?php echo render_star_svg($p['name'], $p['mag'], $p['alt']); ?></td>
<td><?php echo number_format($p['mag'], 1); ?> 等</td>
<td style="font-weight:bold;">
<?php echo $isInvisible ? 'ー' : azimuth_to_16方位($p['az']) . ' <span style="font-size:0.75rem; color:#666; font-weight:normal;">(' . round($p['az']) . '°)</span>'; ?>
</td>
<td>
<?php if (!$isInvisible): ?>
<div class="alt-meter-container">
<div class="alt-meter-bar" style="width: <?php echo $altPercent; ?>%;"></div>
</div>
<?php endif; ?>
<span style="font-weight:bold;"><?php echo round($p['alt']); ?>°</span>
<div style="font-size:0.75rem; color:#666; margin-top:2px;"><?php echo get_altitude_text($p['alt']); ?></div>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endif; ?>
</div>
</div>
</body>
</html>
5. アップロードおよび配置作業手順
-
パソコン上でテキストエディタ(メモ帳、VS Code等)を開き、上記「4. プログラム全コード」の内容を貼り付けてファイル名
index.php(文字コード:UTF-8)として保存します。 -
作成し直した以下のCSVファイル(全5個)をご準備ください。
-
moon_2026_21h.csv -
planets_2026_18h.csv -
planets_2026_21h.csv -
planets_2026_00h.csv -
planets_2026_03h.csv
-
-
FTPソフト(FileZilla等)またはエックスサーバーの「ファイルマネージャ」にログインします。
-
/home/tanzawatake/radish-plan.com/public_html/sun/ディレクトリを開きます。 -
上記の
index.phpと 5つのCSVファイルをすべて同階層へ上書きアップロードします。 -
ブラウザでWebサイトにアクセスし、以下の動作を確認します。
-
都市切り替え(秦野市、盛岡市等)で緯度・経度と日の出入り時刻が正しく変わること。
-
月相カードに「天文台公表月齢」「見かけ寸法」「見える方位」「高さ(高度)」の4項目が正しく表示されていること。
-
惑星カードの右横にある時間選択(18時/21時/00時/03時)を切り替えた際、選択した時間の文字が維持され、対応する高度・方位データが切り替わること。
-
00時および03時選択時に、翌日の日付のデータが正しく読み込まれていること。
-

コメント