
太陽活動によって変化する「宇宙天気」を、iPhoneのホーム画面から手軽にチェックできるScriptableウィジェットです。
NOAA(アメリカ海洋大気庁)が公開している宇宙天気データを取得し、現在のKp指数や太陽風の速度を色付きゲージで表示します。
Kp指数が高くなると地磁気が活発になり、強い磁気嵐が発生することがあります。オーロラが普段より低い緯度で観測される可能性も高くなるため、オーロラ観測の目安としても楽しめます。
小・中・大、すべてのウィジェットサイズに対応しています。
ウィジェットの表示内容
NOAAの地磁気・太陽風データとKp予測を取得します。
サイズごとの表示
- 小:Kp指数・太陽風の状況と活動の目安を表示します。
- 中:日本でのオーロラ観測の目安も確認できます。
- 大:今後24時間のKp予測など、予測情報を詳しく表示します。
設定手順
上から順に進めてください。コードの保存と設定のコピーに必要な操作欄は、それぞれの手順の中にあります。
- Scriptableを用意する
App StoreでScriptableを開き、iPhoneにインストールします。すでに入っている場合は次へ進んでください。
- コードをコピーして保存する
下のコード欄の「コピー」を押します。Scriptableへ移動し、「+」から新しいスクリプトを作ってコード全文を貼り付け、宇宙天気・オーロラ予報などの名前で保存してください。
保存したらこのページに戻り、次の手順へ進みます。
// Variables used by Scriptable. // These must be at the very top of the file. Do not edit. // icon-color: deep-blue; icon-glyph: satellite; // Version: 1.0.0 // ===================================================== // 宇宙天気・オーロラ予報 // NOAA Space Weather Prediction Center // // 小:Kp指数 / 太陽風 // 中:Kp指数 / 太陽風 / オーロラ目安 // 大:Kp指数 / 太陽風 / オーロラ目安 / 24時間Kp予報 // // APIキー不要 // ===================================================== // ===================================================== // NOAA API // ===================================================== const URL_KP = "https://services.swpc.noaa.gov/products/noaa-planetary-k-index.json"; const URL_KP_FORECAST = "https://services.swpc.noaa.gov/products/noaa-planetary-k-index-forecast.json"; const URL_SOLAR_WIND = "https://services.swpc.noaa.gov/products/summary/solar-wind-speed.json"; // ===================================================== // キャッシュ // ===================================================== const fm = FileManager.local(); const cacheDir = fm.joinPath( fm.documentsDirectory(), "space_weather_cache" ); if (!fm.fileExists(cacheDir)) { fm.createDirectory(cacheDir); } const cacheFile = fm.joinPath( cacheDir, "data_v2.json" ); // ===================================================== // JSON取得 // ===================================================== async function loadJSON(url) { const req = new Request(url); req.timeoutInterval = 15; return await req.loadJSON(); } // ===================================================== // NOAAの日付をDateに変換 // ===================================================== function parseNOAADate(value) { if (!value) { return null; } let text = String(value); if ( !text.endsWith("Z") && !text.includes("+") ) { text += "Z"; } const date = new Date(text); if (isNaN(date.getTime())) { return null; } return date; } // ===================================================== // NOAAデータ取得 // ===================================================== async function getSpaceWeather() { try { const [ kpData, windData, forecastData ] = await Promise.all([ loadJSON(URL_KP), loadJSON(URL_SOLAR_WIND), loadJSON(URL_KP_FORECAST) ]); // ================================================= // 現在のKp // ================================================= let kp = null; let kpTime = null; if ( Array.isArray(kpData) && kpData.length > 0 ) { for ( let i = kpData.length - 1; i >= 0; i-- ) { const row = kpData[i]; if (!row) continue; const value = Number(row.Kp); if (isFinite(value)) { kp = value; kpTime = parseNOAADate( row.time_tag ); break; } } } // ================================================= // 太陽風 // ================================================= let solarWind = null; let windTime = null; if ( Array.isArray(windData) && windData.length > 0 ) { for ( let i = windData.length - 1; i >= 0; i-- ) { const row = windData[i]; if (!row) continue; const value = Number( row.proton_speed ); if (isFinite(value)) { solarWind = value; windTime = parseNOAADate( row.time_tag ); break; } } } // ================================================= // Kp予報 // ================================================= const forecasts = []; if ( Array.isArray(forecastData) ) { for ( const row of forecastData ) { if (!row) continue; if ( row.observed !== "predicted" && row.observed !== "estimated" ) { continue; } const value = Number(row.kp); const date = parseNOAADate( row.time_tag ); if ( date && isFinite(value) ) { forecasts.push({ date: date, kp: value, type: row.observed }); } } } // ================================================= // Kp取得チェック // ================================================= if (kp === null) { throw new Error( "Kpデータを解析できませんでした" ); } // ================================================= // キャッシュ保存 // ================================================= const cacheData = { kp: kp, kpTime: kpTime ? kpTime.toISOString() : null, solarWind: solarWind, windTime: windTime ? windTime.toISOString() : null, forecasts: forecasts.map( item => ({ date: item.date.toISOString(), kp: item.kp, type: item.type }) ), updated: Date.now() }; fm.writeString( cacheFile, JSON.stringify(cacheData) ); return { kp: kp, kpTime: kpTime, solarWind: solarWind, windTime: windTime, forecasts: forecasts, updated: Date.now(), cached: false }; } catch (error) { console.log( "NOAA取得エラー: " + error ); // ================================================= // 通信失敗 → キャッシュ // ================================================= if ( fm.fileExists(cacheFile) ) { try { const cached = JSON.parse( fm.readString( cacheFile ) ); return { kp: Number(cached.kp), kpTime: cached.kpTime ? new Date( cached.kpTime ) : null, solarWind: cached.solarWind !== null ? Number( cached.solarWind ) : null, windTime: cached.windTime ? new Date( cached.windTime ) : null, forecasts: ( cached.forecasts || [] ).map( item => ({ date: new Date( item.date ), kp: Number( item.kp ), type: item.type }) ), updated: cached.updated, cached: true }; } catch (e) { console.log( "キャッシュ読込エラー: " + e ); } } return { kp: null, kpTime: null, solarWind: null, windTime: null, forecasts: [], updated: null, cached: false }; } } // ===================================================== // Kpレベル // ===================================================== function getKpLevel(kp) { if (kp === null) { return { label: "取得不可", color: new Color("#8B96AA") }; } if (kp < 2) { return { label: "静穏", color: new Color("#4ADE80") }; } if (kp < 4) { return { label: "やや活発", color: new Color("#A3E635") }; } if (kp < 5) { return { label: "活発", color: new Color("#FACC15") }; } if (kp < 6) { return { label: "磁気嵐 G1", color: new Color("#FB923C") }; } if (kp < 7) { return { label: "磁気嵐 G2", color: new Color("#F97316") }; } if (kp < 8) { return { label: "磁気嵐 G3", color: new Color("#EF4444") }; } if (kp < 9) { return { label: "磁気嵐 G4", color: new Color("#DC2626") }; } return { label: "磁気嵐 G5", color: new Color("#991B1B") }; } // ===================================================== // 太陽風レベル // ===================================================== function getWindLevel(speed) { if (speed === null) { return { label: "取得不可", color: new Color("#8B96AA") }; } if (speed < 400) { return { label: "穏やか", color: new Color("#4ADE80") }; } if (speed < 500) { return { label: "やや強い", color: new Color("#FACC15") }; } if (speed < 700) { return { label: "強い", color: new Color("#FB923C") }; } return { label: "非常に強い", color: new Color("#EF4444") }; } // ===================================================== // 日本でのオーロラ観測目安 // Kpによる簡易判定 // ===================================================== function getJapanAurora(kp) { if (kp === null) { return { icon: "⚪️", label: "判定できません" }; } if (kp < 5) { return { icon: "⚪️", label: "日本での可能性は低い" }; } if (kp < 6) { return { icon: "🟡", label: "北海道でわずかに可能性" }; } if (kp < 7) { return { icon: "🟠", label: "北海道で可能性あり" }; } if (kp < 8) { return { icon: "🔴", label: "北海道で観測に期待" }; } return { icon: "🟣", label: "日本でも注目レベル" }; } // ===================================================== // ゲージ // ===================================================== function addGauge( parent, value, max, color, width ) { const height = 7; const ctx = new DrawContext(); ctx.size = new Size( width, height ); ctx.opaque = false; ctx.respectScreenScale = true; // ================================================= // 背景 // ================================================= const bg = new Path(); bg.addRoundedRect( new Rect( 0, 0, width, height ), height / 2, height / 2 ); ctx.addPath(bg); ctx.setFillColor( new Color( "#FFFFFF", 0.14 ) ); ctx.fillPath(); // ================================================= // 値 // ================================================= if ( value !== null && isFinite(value) && value > 0 ) { const ratio = Math.max( 0, Math.min( value / max, 1 ) ); const gaugeWidth = Math.max( height, width * ratio ); const gauge = new Path(); gauge.addRoundedRect( new Rect( 0, 0, gaugeWidth, height ), height / 2, height / 2 ); ctx.addPath(gauge); ctx.setFillColor( color ); ctx.fillPath(); } const image = ctx.getImage(); const img = parent.addImage( image ); img.imageSize = new Size( width, height ); } // ===================================================== // 24時間以内の最大Kp // ===================================================== function getForecastMax( forecasts ) { if ( !forecasts || forecasts.length === 0 ) { return null; } const now = new Date(); const end = new Date( now.getTime() + 24 * 60 * 60 * 1000 ); let max = null; for ( const item of forecasts ) { if ( item.date >= now && item.date <= end ) { if ( max === null || item.kp > max ) { max = item.kp; } } } return max; } // ===================================================== // 背景 // ===================================================== function setupWidget(widget) { const gradient = new LinearGradient(); gradient.colors = [ new Color("#071426"), new Color("#101B38"), new Color("#171A35") ]; gradient.locations = [ 0, 0.55, 1 ]; widget.backgroundGradient = gradient; } // ===================================================== // 更新時間 // ===================================================== function formatTime(date) { if (!date) { return "--"; } const df = new DateFormatter(); df.locale = "ja_JP"; df.dateFormat = "M/d HH:mm"; return df.string(date); } // ===================================================== // 小サイズ // Kp指数 + 太陽風 // ===================================================== function buildSmall(data) { const widget = new ListWidget(); setupWidget(widget); widget.setPadding( 14, 14, 12, 14 ); const kpLevel = getKpLevel( data.kp ); const windLevel = getWindLevel( data.solarWind ); // ================================================= // タイトル // ================================================= let text = widget.addText( "🌌 宇宙天気" ); text.font = Font.boldSystemFont(13); text.textColor = new Color("#B8C6E3"); widget.addSpacer(7); // ================================================= // Kp指数 // ================================================= text = widget.addText( "Kp指数" ); text.font = Font.mediumSystemFont(9); text.textColor = new Color("#8FA0BE"); const kpRow = widget.addStack(); kpRow.centerAlignContent(); text = kpRow.addText( data.kp !== null ? data.kp.toFixed(1) : "--" ); text.font = Font.boldSystemFont(28); text.textColor = Color.white(); kpRow.addSpacer(7); text = kpRow.addText( kpLevel.label ); text.font = Font.boldSystemFont(10); text.textColor = kpLevel.color; widget.addSpacer(3); addGauge( widget, data.kp, 9, kpLevel.color, 110 ); widget.addSpacer(8); // ================================================= // 太陽風 // ================================================= const windRow = widget.addStack(); windRow.centerAlignContent(); text = windRow.addText( "☀️ 太陽風" ); text.font = Font.mediumSystemFont(9); text.textColor = new Color("#8FA0BE"); windRow.addSpacer(); text = windRow.addText( data.solarWind !== null ? `${Math.round( data.solarWind )} km/s` : "-- km/s" ); text.font = Font.boldSystemFont(10); text.textColor = windLevel.color; widget.addSpacer(4); addGauge( widget, data.solarWind, 800, windLevel.color, 110 ); widget.addSpacer(3); text = widget.addText( windLevel.label ); text.font = Font.boldSystemFont(9); text.textColor = windLevel.color; widget.addSpacer(); // ================================================= // 更新時間 // ================================================= text = widget.addText( `NOAA ${formatTime( data.kpTime )}` ); text.font = Font.systemFont(7); text.textColor = new Color("#65738C"); return widget; } // ===================================================== // 中サイズ // ===================================================== function buildMedium(data) { const widget = new ListWidget(); setupWidget(widget); widget.setPadding( 14, 16, 14, 16 ); const kpLevel = getKpLevel( data.kp ); const windLevel = getWindLevel( data.solarWind ); const aurora = getJapanAurora( data.kp ); // ================================================= // タイトル // ================================================= let text = widget.addText( "🌌 宇宙天気・オーロラ予報" ); text.font = Font.boldSystemFont(13); text.textColor = Color.white(); widget.addSpacer(10); // ================================================= // 2カラム // ================================================= const columns = widget.addStack(); columns.layoutHorizontally(); // ================================================= // 左:Kp指数 // ================================================= const left = columns.addStack(); left.layoutVertically(); left.size = new Size( 135, 0 ); text = left.addText( "Kp指数" ); text.font = Font.mediumSystemFont(10); text.textColor = new Color("#8FA0BE"); text = left.addText( data.kp !== null ? data.kp.toFixed(1) : "--" ); text.font = Font.boldSystemFont(27); text.textColor = Color.white(); left.addSpacer(4); addGauge( left, data.kp, 9, kpLevel.color, 115 ); left.addSpacer(5); text = left.addText( kpLevel.label ); text.font = Font.boldSystemFont(10); text.textColor = kpLevel.color; columns.addSpacer(18); // ================================================= // 右:太陽風 // ================================================= const right = columns.addStack(); right.layoutVertically(); text = right.addText( "太陽風" ); text.font = Font.mediumSystemFont(10); text.textColor = new Color("#8FA0BE"); text = right.addText( data.solarWind !== null ? `${Math.round( data.solarWind )} km/s` : "-- km/s" ); text.font = Font.boldSystemFont(18); text.textColor = Color.white(); right.addSpacer(8); addGauge( right, data.solarWind, 800, windLevel.color, 115 ); right.addSpacer(5); text = right.addText( windLevel.label ); text.font = Font.boldSystemFont(10); text.textColor = windLevel.color; // ================================================= // オーロラ // ================================================= widget.addSpacer(); text = widget.addText( `${aurora.icon} ${aurora.label}` ); text.font = Font.mediumSystemFont(11); text.textColor = new Color("#DCE5F7"); return widget; } // ===================================================== // 大サイズ // ===================================================== function buildLarge(data) { const widget = new ListWidget(); setupWidget(widget); widget.setPadding( 18, 18, 18, 18 ); const kpLevel = getKpLevel( data.kp ); const windLevel = getWindLevel( data.solarWind ); const aurora = getJapanAurora( data.kp ); const forecastMax = getForecastMax( data.forecasts ); // ================================================= // タイトル // ================================================= let text = widget.addText( "🌌 宇宙天気・オーロラ予報" ); text.font = Font.boldSystemFont(16); text.textColor = Color.white(); widget.addSpacer(14); // ================================================= // Kp指数 // ================================================= text = widget.addText( "現在のKp指数" ); text.font = Font.mediumSystemFont(12); text.textColor = new Color("#8FA0BE"); const kpRow = widget.addStack(); kpRow.centerAlignContent(); text = kpRow.addText( data.kp !== null ? data.kp.toFixed(1) : "--" ); text.font = Font.boldSystemFont(36); text.textColor = Color.white(); kpRow.addSpacer(10); text = kpRow.addText( kpLevel.label ); text.font = Font.boldSystemFont(13); text.textColor = kpLevel.color; widget.addSpacer(5); addGauge( widget, data.kp, 9, kpLevel.color, 280 ); widget.addSpacer(18); // ================================================= // 太陽風 // ================================================= const windRow = widget.addStack(); windRow.centerAlignContent(); text = windRow.addText( "☀️ 太陽風" ); text.font = Font.mediumSystemFont(13); text.textColor = new Color("#AAB7D1"); windRow.addSpacer(); text = windRow.addText( data.solarWind !== null ? `${Math.round( data.solarWind )} km/s` : "-- km/s" ); text.font = Font.boldSystemFont(15); text.textColor = data.solarWind !== null ? windLevel.color : new Color("#8B96AA"); widget.addSpacer(8); addGauge( widget, data.solarWind, 800, windLevel.color, 280 ); widget.addSpacer(18); // ================================================= // オーロラ // ================================================= text = widget.addText( "🇯🇵 日本のオーロラ観測目安" ); text.font = Font.mediumSystemFont(12); text.textColor = new Color("#8FA0BE"); widget.addSpacer(6); text = widget.addText( `${aurora.icon} ${aurora.label}` ); text.font = Font.boldSystemFont(16); text.textColor = Color.white(); widget.addSpacer(18); // ================================================= // 24時間Kp予報 // ================================================= text = widget.addText( "24時間以内のKp予報" ); text.font = Font.mediumSystemFont(12); text.textColor = new Color("#8FA0BE"); widget.addSpacer(5); if ( forecastMax !== null ) { const forecastLevel = getKpLevel( forecastMax ); const forecastRow = widget.addStack(); forecastRow.centerAlignContent(); text = forecastRow.addText( `最大 Kp ${forecastMax.toFixed(1)}` ); text.font = Font.boldSystemFont(18); text.textColor = forecastLevel.color; forecastRow.addSpacer(8); text = forecastRow.addText( forecastLevel.label ); text.font = Font.mediumSystemFont(12); text.textColor = new Color("#DCE5F7"); } else { text = widget.addText( "予報データを取得できません" ); text.font = Font.systemFont(12); text.textColor = new Color("#8B96AA"); } widget.addSpacer(); // ================================================= // 更新情報 // ================================================= let footer = `NOAA / Kp ${formatTime( data.kpTime )}`; if (data.cached) { footer += " / キャッシュ"; } text = widget.addText( footer ); text.font = Font.systemFont(9); text.textColor = new Color("#71809B"); return widget; } // ===================================================== // データ取得 // ===================================================== const data = await getSpaceWeather(); // ===================================================== // ウィジェット作成 // ===================================================== let widget; if ( config.widgetFamily === "small" ) { widget = buildSmall(data); } else if ( config.widgetFamily === "large" ) { widget = buildLarge(data); } else { widget = buildMedium(data); } // ===================================================== // Scriptableアプリ内プレビュー // ===================================================== if (!config.runsInWidget) { const alert = new Alert(); alert.title = "プレビュー"; alert.message = "表示するサイズを選択してください。"; alert.addAction("小"); alert.addAction("中"); alert.addAction("大"); const choice = await alert.presentSheet(); if (choice === 0) { widget = buildSmall(data); await widget.presentSmall(); } else if (choice === 1) { widget = buildMedium(data); await widget.presentMedium(); } else if (choice === 2) { widget = buildLarge(data); await widget.presentLarge(); } } else { Script.setWidget( widget ); } Script.complete(); - ホーム画面に追加して設定する
iPhoneのホーム画面を長押しし、ウィジェット追加画面からScriptableを選びます。この記事の対応サイズの説明に合わせてサイズを選んで追加してください。
追加したウィジェットを長押しして「ウィジェットを編集」を開き、「Script」に保存したスクリプトを選びます。
「Parameter」は空欄で構いません。
編集画面を閉じて表示を確認します。ホーム画面への反映には少し時間がかかる場合があります。
Kp指数とは?
Kp指数は、地球の磁場がどれくらい乱れているかを表す数値で、0〜9で表示されます。
数値が低いほど地磁気は静穏で、数値が高くなるほど活動が活発になります。
このウィジェットでは、数値だけでなく緑・黄・オレンジ・赤のゲージでも活動状況が分かるようにしています。
Kp 5以上になると磁気嵐の領域となり、G1〜G5のレベルも表示します。
太陽風とは?
太陽から宇宙空間へ流れ出している粒子の流れです。
通常は数百km/s程度ですが、太陽活動が活発になると速度が大きく上昇することがあります。
このウィジェットでは現在の速度を km/s で表示し、
「穏やか」「やや強い」「強い」「非常に強い」
のように分かりやすく表示します。
日本のオーロラ観測目安について
Kp指数をもとに、日本でオーロラが観測できる可能性を簡易的に表示しています。
Kp指数が高くなると北海道などでも低緯度オーロラが観測される可能性があります。
ただし、実際にオーロラが見えるかどうかは、太陽活動・地磁気・観測場所・天候などさまざまな条件に左右されます。
あくまで観測の目安としてご利用ください。
動作確認・使用結果
| 確認した端末 | iPhone 17 ProMAX |
|---|---|
| iOS | iOS27 |
| Scriptable | 1.7.19 |
| 確認したサイズ | 小・中・大 |
| バージョン | 1.0.0 |
| 動作確認日 | 2026-09-22 |
表示・使用時のポイント
NOAAの地磁気・太陽風データとKp予測を取得します。観測値と予測値は異なるため、日時と項目名を合わせて確認してください。
更新履歴
1.0.0
- 初回リリース
よくあるエラーと対処
- 確認すること
- 表示されない場合は、ウィジェット編集画面の「Script」に保存したスクリプトが選ばれているかも確認してください。コードを貼り直すときは全文を置き換えます。
- Kp予測の欄が空になる
- 予測データが未公開か、取得できていない場合があります。時間を置いて再実行してください。
- 値が更新されない
- 通信状態とデータの日時を確認してください。保存済みの値が使われる場合があります。
注意事項・制限
- Kp指数だけで、その場所からオーロラが必ず見えるとは判断できません。
- 天候・緯度・空の明るさなど現地の条件でも見え方が変わります。
- ホーム画面の更新タイミングはiOSが管理するため、操作やデータの変更がすぐに反映されない場合があります。
データ提供・参考資料
NOAA Space Weather Prediction Center:Kp指数・Kp予測・太陽風データ。
SHARE

