15.雨雲レーダーのアニメーションの仕方

今回は雨雲レーダーのデータが、3時間前から1時間後まで存在するので、スクロールバーを用いてアニメーションの仕方をご説明いたします。


では早速、雨雲レーダーのコードを見てみましょう‼️🌧️☔⛈️

PHPでgetしたナウキャストjsonデータを変数$baseTimeに代入しています。

<?php
 // ナウキャストjson3時間前までのデータのアドレス
    $url = "https://www.jma.go.jp/bosai/jmatile/data/nowc/targetTimes_N1.json";
 
 // ナウキャストjson1時間後までのデータのアドレス
    $url2 = "https://www.jma.go.jp/bosai/jmatile/data/nowc/targetTimes_N2.json";

    $json1 = file_get_contents($url1);
    $json2 = file_get_contents($url2);
    $data1 = json_decode($json1,true);
    $data2 = json_decode($json2,true);

    $baseTimes1 = array_column($data1, 'basetime');
    $validTimes1 = array_column($data1, 'validtime');
    $baseTimes2 = array_column($data2, 'basetime');
    $validTimes2 = array_column($data2, 'validtime');
?>

PHPでgetしたナウキャストjsonデータをarray_column関数を使用して、$data1および$data2からbasetimeとvalidtimeという特定のキーの値をそれぞれ抽出し、$basetime1、$validtime1、$basetime2、$validtime2という配列に格納しています。

このコードの目的は、気象データのbasetime(基準時刻)とvalidtime(有効時刻)を抽出することです。

<script>
var baseTimes1 = <?php echo json_encode($baseTimes1); ?>;
var validTimes1 = <?php echo json_encode($validTimes1); ?>;
var baseTimes2 = <?php echo json_encode($baseTimes2); ?>;
var validTimes2 = <?php echo json_encode($validTimes2); ?>;

var nowCastLayer = [];
for (var i = baseTimes1.length - 1, j = 0; i >= 0; i--, j++) {
    nowCastLayer[j] = L.tileLayer(`https://www.jma.go.jp/bosai/jmatile/data/nowc/${baseTimes1[i]}/none/${validTimes1[i]}/surf/hrpns/{z}/{x}/{y}.png`, {zIndex: 20, maxNativeZoom: 10, opacity: 0.6});
}
for (var t = 0; t < baseTimes2.length; t++) {
    nowCastLayer[baseTimes1.length + t] = L.tileLayer(`https://www.jma.go.jp/bosai/jmatile/data/nowc/${baseTimes2.slice().reverse()[t]}/none/${validTimes2.slice().reverse()[t]}/surf/hrpns/{z}/{x}/{y}.png`, {zIndex: 20, maxNativeZoom: 10, opacity: 0.6});
}

// 初期表示のレイヤーを地図に追加する(現在)
var initialIndex = baseTimes1.length; // 初期のインデックスを設定 (現在)
map.addLayer(nowCastLayer[initialIndex]);
</script>

ここではPHPから配列($baseTimes1、$validTimes1など)が提供されており、それをJavaScriptの変数baseTimes1、validTimes1、baseTimes2、validTimes2に変換しています。

json_encode関数を用いてPHP配列をJSON形式に変換しているため、データがJavaScript配列として扱えます。

for (var i = baseTimes1.length - 1, j = 0; i >= 0; i--, j++) {
    nowCastLayer[j] = L.tileLayer(`https://www.jma.go.jp/bosai/jmatile/data/nowc/${baseTimes1[i]}/none/${validTimes1[i]}/surf/hrpns/{z}/{x}/{y}.png`, {zIndex: 20, maxNativeZoom: 10, opacity: 0.6});
}

ナウキャストjson3時間前までのデータのアドレスが、下記になります。

$url = "https://www.jma.go.jp/bosai/jmatile/data/nowc/targetTimes_N1.json";

このjsonデータは現在をbasetime1[0]として、3時間前のbasetime1[36]まであります。

スライドバーの一番左端をbasetime1[36]とする為、最初のforループはbaseTimes1とvalidTimes1を基に、降水ナウキャストのURLを使用してタイルレイヤーを作成し、逆順に格納しています。

for (var t = 0; t < baseTimes2.length; t++) {
    nowCastLayer[baseTimes1.length + t] = L.tileLayer(`https://www.jma.go.jp/bosai/jmatile/data/nowc/${baseTimes2.slice().reverse()[t]}/none/${validTimes2.slice().reverse()[t]}/surf/hrpns/{z}/{x}/{y}.png`, {zIndex: 20, maxNativeZoom: 10, opacity: 0.6});
}

ナウキャストjson1時間後までのデータのアドレスが、下記になります。

$url2 = "https://www.jma.go.jp/bosai/jmatile/data/nowc/targetTimes_N2.json";

このjsonデータは、validTimes2[0]が1時間後の予測データ、validTimes2[11]が5分後の予測データになります。

全部で12個の配列が、validTimes2に格納されています。

${validTimes2.slice().reverse()[t]}は、配列validTimes2の要素を逆順にして、その逆順にした配列のt番目の要素を取得する操作を行っています。

具体的な処理は以下のようになります:

  1. validTimes2.slice()
    • slice()メソッドは、配列のコピーを作成するために使われています。この場合、元の配列validTimes2に変更を加えず、コピーした新しい配列を作成します。
  2. .reverse()
    • コピーされた配列に対してreverse()メソッドが使われ、配列の要素順が逆になります。つまり、validimes2が [a, b, c] なら、validTimes2.slice().reverse()は [c, b, a] となります。
    • この操作により、元のvalidTimes2配列には影響を与えずに、逆順にした配列が得られます。
  3. [t]
    • tは配列のインデックスを指定する変数で、逆順にされた配列のt番目の要素が取得されます。

例えば、validTimes2が [1, 2, 3, 4] で、tが 0 の場合、

${validTimes2.slice().reverse()[t]} は 4 となります。

nowCastLayer[baseTimes1.length + t]としているのは、スクロールバーで表示順序をbaseTimes1.length(現在)の次にvalidTimes2[0]を配置している為です。


// 初期表示のレイヤーを地図に追加する(現在)
var initialIndex = baseTimes1.length; // 初期のインデックスを設定 (現在)
map.addLayer(nowCastLayer[initialIndex]);

var currentIndex = initialIndex;
var intervalId = null;

document.getElementById('scrollbar').min = 0;
document.getElementById('scrollbar').max = nowCastLayer.length - 1;
document.getElementById('scrollbar').value = initialIndex;

document.getElementById('scrollbar').oninput = function() {
  updateMapLayer(parseInt(this.value));
};

document.getElementById('play-pause').onclick = function() {
  if (intervalId) {
    clearInterval(intervalId);
    intervalId = null;
    this.textContent = '再生';
  } else {
    intervalId = setInterval(function() {
      if (currentIndex < nowCastLayer.length - 1) {
        currentIndex++;
      } else {
        currentIndex = 0;
      }
      document.getElementById('scrollbar').value = currentIndex;
      updateMapLayer(currentIndex);
    }, 500);
    this.textContent = '停止';
  }
};

function updateMapLayer(index) {
  nowCastLayer.forEach(function(layer) {
    map.removeLayer(layer);
  });
  map.addLayer(nowCastLayer[index]).bringToFront();
  currentIndex = index;
}

1.初期レイヤーの追加

// 初期表示のレイヤーを地図に追加する(現在)
var initialIndex = baseTimes1.length; // 初期のインデックスを設定 (現在)
map.addLayer(nowCastLayer[initialIndex]);

スクロールバーの初期値をbasetime1[0](現在)とする為に、var initialIndex = baseTimes1.length;で37番目の値を初期値basetime1[0](現在)とし、

addLayer(...)メソッドで、指定したレイヤーを地図上に追加しています。

2.グローバル変数の設定

var currentIndex = initialIndex;
var intervalId = null;

currentIndexは現在表示しているレイヤーのインデックスを保持します。

intervalIdは、再生/停止ボタンのタイマーIDを保持し、再生中か停止中かを判別します。

3.スクロールバー(スライダー)の設定

document.getElementById('scrollbar').min = 0;
document.getElementById('scrollbar').max = nowCastLayer.length - 1;
document.getElementById('scrollbar').value = initialIndex;

スクロールバー(スライダー)の最小値と最大値を設定しています。

スライダーの範囲はnowCastLayerのインデックス範囲に合わせられています。

初期値としてinitialIndexを設定し、初期レイヤーの位置を反映させています。

4.HTMLコードのスクロールバーの設定

下記コードを<body>の下に書いておきます。

<div id="scrollbar-container">
    <input type="range" min="0" max="48" step="1" value="0" id="scrollbar">
    <div id="scrollbar-labels">
        <!-- JavaScriptでラベルを挿入 -->
    </div>
    <button id="play-pause">再生</button>
</div>
  1. スライダー(<input type="range" ... id="scrollbar">)
    • <input type="range">は、ユーザーがスライド操作で値を変更できるスクロールバーです。
    • min="0": スクロールバーの最小値が0です。
    • max="48": スクロールバーの最大値が48です。この範囲内でインデックスを指定し、データの表示ができます。
    • step="1": 値が1ずつ増減する設定です。
    • value="0": 初期値として0を設定しています。
    • id="scrollbar": このスクロールバーに対してJavaScriptやCSSからアクセスするためのIDです。コードでイベントリスナーを追加して、ユーザーがスクロールバーを動かすたびに表示が更新されるようにしています。
  2. ラベル用のコンテナ(<div id="scrollbar-labels">
    • id="scrollbar-labels"はラベルを挿入するためのコンテナです。JavaScriptを使って動的にスクロールバーの位置に応じたラベル(例えば時間や日付などの情報)を追加する予定で、ラベルのデータが動的に生成されるようになっています。
  3. 再生/停止ボタン(<button id="play-pause">再生</button>)
    • ボタンタグで、スクロールバーの自動再生や停止を制御します。
    • id="play-pause"はJavaScriptからアクセスするためのIDで、再生/停止ボタンの機能が実装され、クリックするたびに再生と停止が切り替わります。
    • 初期表示のテキストは「再生」として設定され、ユーザーが一度クリックすると再生が開始され、ボタンの表示が「停止」に変わる仕組みです。

この構成により、スクロールバーと再生ボタンを使って地図の気象データの表示をユーザーが手動または自動でコントロールできるインターフェースが提供されます。


5.スクロールバーの操作イベント

document.getElementById('scrollbar').oninput = function() {
  updateMapLayer(parseInt(this.value));
};

document.getElementById('scrollbar')

  • HTML内のidがscrollbarの要素(スクロールバー)を取得します。

②oninputイベント

  • oninputイベントは、スクロールバーの値が変更されるたびに発生します。ユーザーがスライダーを動かすたびにこのイベントがトリガーされます。

③無名関数の実行

  • oninputイベントに無名関数(function() {...})が設定され、スクロールバーの値が変更されるとこの関数が実行されます。

parseInt(this.value)

  • this.valueでスクロールバーの現在の値を取得し、parseIntで整数に変換します。スクロールバーの値は、インデックスとして扱われるため、整数にする必要があります。

updateMapLayer(parseInt(this.value))

  • 取得したインデックス(スクロールバーの値)をupdateMapLayer関数に引数として渡します。
  • updateMapLayer関数は、指定されたインデックスに基づいて地図のレイヤーを更新する役割を果たします。これにより、ユーザーがスクロールバーを動かすと、その位置に対応する気象データやレイヤーが地図上に表示されます。

6.地図レイヤーを更新する関数

function updateMapLayer(index) {
  nowCastLayer.forEach(function(layer) {
    map.removeLayer(layer);
  });
  map.addLayer(nowCastLayer[index]).bringToFront();
  currentIndex = index;
}
  • updateMapLayerは、地図上のレイヤーを更新する関数です。
    • nowCastLayer配列内の全てのレイヤーをmap.removeLayerで削除します。
    • 指定されたインデックスindexに対応するレイヤーをmap.addLayerで追加し、bringToFront()メソッドで最前面に表示します。
    • currentIndexを更新し、現在表示されているレイヤーのインデックスとして保持します。

このコードにより、地図上に気象情報を時系列でスムーズに表示し、スクロールバーや再生ボタンでユーザーが簡単に操作できるようになっています。

7.再生/停止ボタンの設定

document.getElementById('play-pause').onclick = function() {
  if (intervalId) {
    clearInterval(intervalId);
    intervalId = null;
    this.textContent = '再生';
  } else {
    intervalId = setInterval(function() {
      if (currentIndex < nowCastLayer.length - 1) {
        currentIndex++;
      } else {
        currentIndex = 0;
      }
      document.getElementById('scrollbar').value = currentIndex;
      updateMapLayer(currentIndex);
    }, 500);
    this.textContent = '停止';
  }
};

このコードは、再生/停止ボタン(play-pause)のクリックに応じて、自動で地図のレイヤーを切り替えるか、または停止する機能を実装しています。

①document.getElementById('play-pause').onclick

  • HTML要素id="play-pause"のボタンに対し、クリックイベントリスナーを設定しています。このボタンがクリックされると、次の処理が行われます。

②if (intervalId)条件

  • intervalIdが設定されている(再生中)かどうかを確認します。
    • 再生中(intervalIdが存在する): 現在のアニメーションを停止します。
    • 停止中(intervalIdがnull): 新たにタイマーをセットしてアニメーションを開始します。

③アニメーション停止処理

clearInterval(intervalId);
intervalId = null;
this.textContent = '再生';
  • clearInterval(intervalId)でタイマーを解除してアニメーションを停止します。
  • intervalIdをnullにリセットし、停止中の状態を示します。
  • ボタンの表示テキスト(textContent)を「再生」に変更し、次に再生できる状態であることを示します。

④アニメーション開始処理

intervalId = setInterval(function() {
  if (currentIndex < nowCastLayer.length - 1) {
    currentIndex++;
  } else {
    currentIndex = 0;
  }
  document.getElementById('scrollbar').value = currentIndex;
  updateMapLayer(currentIndex);
}, 500);
this.textContent = '停止';
  • setIntervalで500ミリ秒(0.5秒)ごとに無名関数が実行され、intervalIdにタイマーIDが保存されます。
  • この無名関数では、以下の処理が行われます:
  • インデックス更新: currentIndexが配列の最後に達していなければ1つ増やします。最後に達していれば0にリセットし、最初から繰り返します。
  • スクロールバー位置の更新: スクロールバー(scrollbar)の値をcurrentIndexに更新し、スクロールバー位置と表示レイヤーを同期します。
  • レイヤー更新: updateMapLayer(currentIndex)を呼び出し、currentIndexに対応するレイヤーを地図上に表示します。
  • ボタンのテキストを「停止」に変更し、再生中であることを示します。

このコードにより、再生ボタンをクリックすることで地図上の気象レイヤーが0.5秒ごとに自動で切り替わり、スライドショーのような視覚的アニメーションが得られます。再度クリックするとアニメーションが停止し、レイヤーが固定されます。


8.スクロールバーのラベルを設定

最後に、スクロールバーのラベル設定を説明します。

このコードは、気象データのタイムラインを表すスクロールバーに、各時刻のラベルを表示するためのHTMLを動的に生成しています。

タイムラインには、各時間に対応するラベルが一定の間隔で配置され、表示されます。

// basetimeに9時間を加える関数
function addNineHours(time) {
    return moment(time, 'YYYYMMDDHHmm').add(9, 'hours').format('HH:mm');
}

// スクロールバーのラベルを設定
var labelsHtml = [];
labelsHtml.push(${addNineHours(baseTimes1[36])});
labelsHtml.push(${addNineHours(baseTimes1[30])});
labelsHtml.push(${addNineHours(baseTimes1[24])});
labelsHtml.push(${addNineHours(baseTimes1[18])});
labelsHtml.push(${addNineHours(baseTimes1[12])});
labelsHtml.push(${addNineHours(baseTimes1[6])});

// "現在"のラベルを追加し、時刻も表示(位置を右にずらす)
labelsHtml.push(${addNineHours(baseTimes1[0])}現在);

// baseTimes2のラベルを追加
labelsHtml.push(${addNineHours(validTimes2[6])});
labelsHtml.push(${addNineHours(validTimes2[0])});

document.getElementById('scrollbar-labels').innerHTML = labelsHtml.join("");

①9時間を加える関数 addNineHours

function addNineHours(time) {
    return moment(time, 'YYYYMMDDHHmm').add(9, 'hours').format('HH:mm');
}

世界各地の標準時は協定世界時(UTC)を基準として定められており、日本標準時(JST)は、協定世界時より9時間進んでいます(東経135度分の時差)。

このことから、日本標準時にする為に9時間を加えています。

  • 引数として受け取ったtimeに9時間を加えて、新しい時刻をHH:mm形式(例: 14:00)で返す関数です。
  • momentライブラリを使って、文字列timeをYYYYMMDDHHmm形式で解析し、9時間を追加した結果をフォーマットして返しています。これにより、JST(日本標準時)としてラベルを表示することが可能になります。

②"現在"のラベル追加

labelsHtml.push(`${addNineHours(baseTimes1[0])}現在`);
  • baseTimes1[0]は最新の基準時間(現在)を指し、これに9時間を加えて「現在」のラベルを表示します。
  • left: 76.5%に配置しているため、スクロールバーの適切な位置にラベルが表示されます。

③ラベルの挿入

document.getElementById('scrollbar-labels').innerHTML = labelsHtml.join("");
  • 配列labelsHtmlの要素をすべて文字列として結合し、scrollbar-labelsというIDを持つ要素のinnerHTMLに設定します。
  • これにより、HTML要素内に生成したラベルのHTMLが挿入され、スクロールバー上に各時刻を表すラベルが表示されます。

このコードにより、ユーザーがスクロールバー上で時刻を視覚的に確認しやすくなり、地図の気象データがどの時刻に対応するかが直感的に理解できるようになります。


お疲れ様でした‼️

ここまで雨雲レーダーのアニメーション化の仕方について、解説しました。

私も雨雲レーダーのアニメーション化は、大変苦労しました。

それでもChatGPTを駆使して、作成する事が出来ました。

この記事を読んで頂き、皆さまが雨雲レーダーのアニメーション化が実現することを心より願っております。

分からないことがあれば、ChatGPTに聞きまくるのがお勧めです🤖

ご武運をお祈りしております‼️

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

CAPTCHA