🐱 MechaTora 開発ブログ

地震モニターアプリ:リアルタイム更新の技術的仕組み

JavaScript API連携 リアルタイム処理 防災アプリ Web開発

はじめに:なぜ地震モニターアプリを作ったのか

2024年1月の能登半島地震、そして日々感じる小さな揺れ。地震大国である日本に住む私たちにとって、「今、どこで地震が起きているか」をリアルタイムで知ることは非常に重要です。

既存の地震情報アプリも多数ありますが、「もっとシンプルに」「Webブラウザで気軽にチェックできる」「無料で誰でも使える」ものが欲しいと思い、地震モニターアプリを開発しました。

本記事では、このアプリの核となるリアルタイム更新機能の技術的な仕組みを詳しく解説します。JavaScriptでどのようにAPIと通信し、データを取得・表示するのか、具体的なコードを交えて説明していきます。

🎯 この記事で学べること

  • 気象庁XML形式の地震情報APIとの連携方法
  • JavaScriptの非同期処理(fetch API)の実装
  • 自動更新ロジックの設計と実装
  • XMLデータのパースと表示処理
  • エラーハンドリングとユーザー体験の向上

課題1:どのAPIを使うか - 気象庁データの選定

地震情報を提供するAPIはいくつか存在しますが、今回は気象庁が提供する公式データを利用することにしました。理由は以下の3点です。

  • 信頼性が高い:気象庁の公式データなので情報の正確性が保証されている
  • 無料で利用可能:個人開発でも気軽に使える(商用利用でも問題なし)
  • リアルタイム性:地震発生から数分以内に情報が更新される

しかし、気象庁のデータには一つ大きな課題がありました。それは「XML形式」でデータが提供されるという点です。JSONに慣れた現代のWeb開発者にとって、XMLのパース処理は少々面倒です。

📌 ポイント: API選定では「無料」「信頼性」「更新頻度」のバランスが重要。個人開発では特にコスト面も考慮する必要があります。

課題2:データ取得の実装 - fetch APIとCORS対策

気象庁のデータを取得するには、JavaScriptのfetch APIを使います。しかし、ここで大きな壁が立ちはだかりました。それがCORS(Cross-Origin Resource Sharing)エラーです。

CORS問題とは?

Webブラウザには「異なるドメインのリソースを勝手に取得できない」というセキュリティ制約があります。私のサイト(mechatora.com)から気象庁のサイト(jma.go.jp)のデータを直接取得しようとすると、ブラウザがこれをブロックしてしまうのです。

この問題を解決するために、CORSプロキシサービスを経由してデータを取得する方法を採用しました。

// 地震情報を取得する関数 async function fetchEarthquakeData() { const API_URL = 'https://www.jma.go.jp/bosai/quake/data/list.json'; const PROXY_URL = 'https://api.allorigins.win/raw?url='; try { // CORSプロキシ経由でデータ取得 const response = await fetch(PROXY_URL + encodeURIComponent(API_URL)); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const data = await response.json(); return data; } catch (error) { console.error('データ取得エラー:', error); displayError('地震情報の取得に失敗しました'); return null; } }
📌 注意点: CORSプロキシは便利ですが、本番環境では自前のプロキシサーバーを立てる方が安定性・セキュリティ面で望ましいです。

課題3:データのパースと表示 - XMLからHTMLへ

取得したJSONデータには、最新の地震情報が配列形式で格納されています。これをユーザーが見やすい形式に変換する必要があります。

データ構造の理解

気象庁のJSON形式データは以下のような構造になっています。

// 取得されるデータの例 [ { "id": "20251020120000", "time": "2025-10-20T12:00:00+09:00", "earthquake": { "origin_time": "2025-10-20T11:55:00+09:00", "hypocenter": { "name": "茨城県南部", "latitude": 36.1, "longitude": 140.1, "depth": 50 }, "magnitude": 4.2 }, "points": [ { "pref": "茨城県", "addr": "水戸市", "scale": 40 // 震度4 } ] } ]

表示用HTMLの生成

このデータを元に、以下のような処理で表示用のHTMLを動的生成します。

function displayEarthquakeList(earthquakes) { const container = document.getElementById('earthquake-list'); container.innerHTML = ''; // 既存の内容をクリア // 最新10件を表示 earthquakes.slice(0, 10).forEach((eq, index) => { const card = document.createElement('div'); card.className = 'earthquake-card'; // 震度によって色分け const maxScale = Math.max(...eq.points.map(p => p.scale)); const scaleClass = getScaleColorClass(maxScale); card.innerHTML = `
震度${Math.floor(maxScale / 10)} ${formatTime(eq.earthquake.origin_time)}

震源地: ${eq.earthquake.hypocenter.name}

M${eq.earthquake.magnitude} / 深さ${eq.earthquake.hypocenter.depth}km

${eq.points.slice(0, 5).map(p => `${p.pref} ${p.addr}` ).join('')}
`; container.appendChild(card); }); } // 震度に応じた色クラスを返す function getScaleColorClass(scale) { if (scale >= 60) return 'scale-severe'; // 震度6以上: 赤 if (scale >= 50) return 'scale-strong'; // 震度5: オレンジ if (scale >= 40) return 'scale-medium'; // 震度4: 黄色 return 'scale-light'; // 震度3以下: 青 }

このコードのポイントは、震度の大きさに応じて視覚的に色分けすることで、ユーザーが一目で危険度を判断できるようにしている点です。

課題4:自動更新ロジック - setIntervalとメモリ管理

地震情報は刻一刻と変化します。ユーザーが手動で更新ボタンを押さなくても、自動的に最新情報が表示される仕組みが必要です。

自動更新の実装

let updateInterval = null; const UPDATE_INTERVAL_MS = 60000; // 60秒ごとに更新 // 自動更新を開始 function startAutoUpdate() { // 初回データ取得 updateEarthquakeData(); // 定期的に更新 updateInterval = setInterval(() => { updateEarthquakeData(); updateLastUpdateTime(); }, UPDATE_INTERVAL_MS); } // 自動更新を停止 function stopAutoUpdate() { if (updateInterval) { clearInterval(updateInterval); updateInterval = null; } } // ページ離脱時にクリーンアップ window.addEventListener('beforeunload', () => { stopAutoUpdate(); }); // データ更新処理 async function updateEarthquakeData() { const loadingIndicator = document.getElementById('loading'); loadingIndicator.style.display = 'block'; const data = await fetchEarthquakeData(); if (data && data.length > 0) { displayEarthquakeList(data); showSuccessMessage('最新情報に更新されました'); } loadingIndicator.style.display = 'none'; }
📌 メモリリーク対策: setIntervalを使う場合、必ずclearIntervalでクリーンアップすることが重要です。特にSPA(Single Page Application)では、ページ遷移時に必ず停止処理を実行しましょう。

課題5:エラーハンドリングとUX改善

ネットワークエラー、APIのタイムアウト、データ形式の変更など、予期せぬエラーは必ず発生します。重要なのは、エラーが起きた時にユーザーに適切なフィードバックを返すことです。

実装したエラー処理

  • ネットワークエラー:「通信エラーが発生しました。しばらく待ってから再度お試しください」
  • データ取得失敗:「地震情報の取得に失敗しました。気象庁のサーバーが一時的に利用できない可能性があります」
  • データが空:「現在、表示可能な地震情報はありません」
function displayError(message) { const container = document.getElementById('earthquake-list'); container.innerHTML = `
⚠️

${message}

`; } // ローディング状態の表示 function showLoading(isLoading) { const loader = document.getElementById('loading-spinner'); const content = document.getElementById('earthquake-list'); if (isLoading) { loader.style.display = 'flex'; content.style.opacity = '0.5'; } else { loader.style.display = 'none'; content.style.opacity = '1'; } }

また、最終更新時刻を表示することで、「今見ているデータがいつの時点のものか」をユーザーが把握できるようにしています。

まとめ:リアルタイムWebアプリ開発で学んだこと

地震モニターアプリの開発を通じて、以下のような技術的知見を得ることができました。

🎓 開発を通じて得られた学び

  • 外部API連携の基本:fetch APIの使い方、CORS対策、エラーハンドリング
  • 非同期処理の設計:async/awaitパターン、Promise処理、メモリリーク対策
  • リアルタイム更新:setIntervalの適切な使用、クリーンアップ処理の重要性
  • UX設計:ローディング表示、エラーメッセージ、色による視覚的フィードバック
  • データ変換:XML/JSONパース、表示用データへの整形

特に印象的だったのは、「技術的に正しい実装」と「ユーザーが使いやすい実装」は必ずしも一致しないという点です。例えば、データ更新を5秒ごとに行えば技術的には「よりリアルタイム」ですが、ユーザーにとっては画面がチカチカして見づらくなります。

最終的に60秒間隔に落ち着いたのは、複数のユーザーテストを経て「実用性とリアルタイム性のバランスが最も良い」という結論に至ったからです。

今後の改善予定

現在のバージョンは基本機能の実装に留まっていますが、今後は以下の機能追加を予定しています。

  • 地図上での震源地表示(Google Maps API連携)
  • プッシュ通知機能(震度5以上の地震発生時)
  • 過去の地震履歴検索機能
  • 地域別フィルタリング機能
  • PWA化によるオフライン対応

非エンジニアでも、JavaScriptの基本を理解していればこのような実用的なアプリを作ることは十分可能です。ぜひ皆さんも、身近な課題を解決するWebアプリ開発にチャレンジしてみてください。