気象庁APIのJsonを利用する為のJavascript
気象庁のJsonデータをごにょごにょする関数w
気象庁APIをご存じだろうか。下記のJsonデータを解析利用する為の関数
JavaScript
// 気象庁
// 天気概況(週間)
// https://www.jma.go.jp/bosai/nowc/#area_type=class20s&area_code=1310500
// 東京 130000 千葉 120000
// areaCode = "130000";
// 文京区 1310500 八丈島 1340100 銚子 1220200 小笠原村 1342100
// cityCode="1310500";
// ////////////////////////////////////////////////////
function newArea(){
// エリア初期値 東京都・文京区
areaCode = "130000";
cityCode = "1310500";
class10 = 0;
// Get 引数取得
let resGet = GetQueryString();
if (resGet.area != null){
areaCode = resGet.area;
let cityCodeGet = resGet.city;
let cityCodeAry = cityCodeGet.split(':');
cityCode = cityCodeAry[1];
class10 = cityCodeAry[0];
}
}
async function weatherJmaApi() {
newArea();
if (areaCode === 460040) areaCode = 460100;
const now = new Date();
const an_hour_ago = new Date(now.getTime() - 1000 * 60 * 15);
let hh = formatDate(an_hour_ago, 'hh');
const date = formatDate(an_hour_ago, 'YYYYMMDD');
const url = `https://www.jma.go.jp/bosai/forecast/data/forecast/${areaCode}.json`;
const data = await getData(url);
const class10s = Math.min(Math.max(class10, 0), 1);
const area = data[0].timeSeries[0].areas[class10s];
let areaTemp = data[1].timeSeries[1].areas[class10s] || data[1].timeSeries[1].areas[class10s - 1];
const todayTempMax = data[0].timeSeries[2].areas[class10s].temps[1];
const code = data[0].timeSeries[2].areas[class10s]?.area.code;
hh = Number(hh) < 18 ? '00' : '18';
const urlAmedas = `https://www.jma.go.jp/bosai/amedas/data/point/${code}/${date}_${hh}.json`;
const dataAmedas = await getData(urlAmedas);
const amedasDate = formatDate(new Date(), 'YYYYMMDD') + (hh === '00' ? '000000' : '180000');
const maxAmedas = dataAmedas !== "Error" ? dataAmedas[amedasDate]?.maxTemp?.[0] || "" : "";
const minAmedas = dataAmedas !== "Error" ? dataAmedas[amedasDate]?.minTemp?.[0] || "" : "";
const dayOfWeekStrJP = ["日", "月", "火", "水", "木", "金", "土"];
let output = '<ul id="tenki">';
for (let i = 0; i < 2; i++) {
let tempmax = areaTemp.tempsMax[i] || todayTempMax;
let tempmin = areaTemp.tempsMin[i];
let dateTime = new Date(hh === '00' ? data[0].reportDatetime : data[1].reportDatetime);
const dtStr = dateTime.toTimeString();
if ((dtStr.includes('21:00:00') || dtStr.includes('17:00:00')) && i === 0) {
tempmax = maxAmedas;
tempmin = minAmedas;
}
if ((dtStr.includes('21:00:00') || dtStr.includes('17:00:00')) && i === 1) {
tempmax = data[0].timeSeries[2].areas[class10].temps[1];
tempmin = data[0].timeSeries[2].areas[class10].temps[0];
}
if (dtStr.includes('5:00:00') && i === 1) tempmax = data[0].timeSeries[2].areas[class10].temps[3];
if (dtStr.includes('11:00:00') && i === 1) {
tempmax = data[0].timeSeries[2].areas[class10s].temps[3];
tempmin = data[0].timeSeries[2].areas[class10s].temps[2];
}
const timeDefines = new Date(data[0].timeSeries[0].timeDefines[i]);
const waves = area['waves'] ? area['waves'][i] : '';
const winds = area['winds'][i];
let weathers = (area['weathers'][i] || "").replace("多摩西部", "");
output += `
<li class="item">
<a href="https://www.jma.go.jp/bosai/forecast/#area_type=offices&area_code=${areaCode}" target="_blank">
${timeDefines.getDate()}日(${dayOfWeekStrJP[timeDefines.getDay()]})
</a>
<a href="https://www.jma.go.jp/bosai/forecast/#area_type=class20s&area_code=${cityCode}" target="_blank">
${weathers}</a>
<a href="https://www.jma.go.jp/bosai/#pattern=default&area_type=class20s&area_code=${cityCode}" target="_blank">
${tempmin}-${tempmax}℃ ${waves}m
</a>
<a href="https://www.jma.go.jp/bosai/nowc/#area_type=class20s&area_code=${cityCode}" target="_blank">
${winds}
</a>
</li>`;
}
output += '</ul>';
document.getElementById('weather').innerHTML = hankaku2Zenkaku(output);
}
// 気象庁注意報・警報 文京区コード 1310500
//////////////////////////////////////////////////////////
function warningAlert(alert){
const warningJson ={
"warninginfo":{
"02":'<span class="blinkS2">暴風雪警報</span>',
"03":'<span class="blinkS2">大雨警報</span>',
"04":'<span class="blinkS2">洪水警報</span>',
"05":'<span class="blinkS2">暴風警報</span>',
"06":'<span class="blinkS2">大雪警報</span>',
"07":'<span class="blinkS2">波浪警報</span>',
"08":'<span class="blinkS2">高潮警報</span>',
"10":'<span class="blinkS3">大雨注意報</span>',
"12":'<span class="blinkS3">大雪注意報</span>',
"13":'<span class="blinkS3">風雪注意報</span>',
"14":'<span class="blinkS3">雷注意報</span>',
"15":'<span class="blinkS3">強風注意報</span>',
"16":'<span class="blinkS3">波浪注意報</span>',
"17":'<span class="blinkS3">融雪注意報</span>',
"18":'<span class="blinkS3">洪水注意報</span>',
"19":'<span class="blinkS3">高潮注意報</span>',
"20":'<span class="blinkS3">濃霧注意報</span>',
"21":'<span class="blinkS3">乾燥注意報</span>',
"22":'<span class="blinkS3">なだれ注意報</span>',
"23":'<span class="blinkS3">低温注意報</span>',
"24":'<span class="blinkS3">霜注意報</span>',
"25":'<span class="blinkS3">着氷注意報</span>',
"26":'<span class="blinkS3">着雪注意報</span>',
"32":'<span class="blinkS2">暴風雪特別警報</span>',
"33":'<span class="blinkS2">大雨特別警報</span>',
"35":'<span class="blinkS2">暴風特別警報</span>',
"36":'<span class="blinkS2">大雪特別警報</span>',
"37":'<span class="blinkS2">波浪特別警報</span>'
}
};
let warning = warningJson.warninginfo[alert];
return warning;
}
///////////////////////////////////////////////////////////
// http://www.jma.go.jp/bosai/common/const/area.json
// https://www.jma.go.jp/bosai/warning/data/warning/130000.json
// https://www.jma.go.jp/bosai/warning/#area_type=class20s&area_code=1310500&lang=ja
async function weatherAlertApi() {
newArea();
const url = `https://www.jma.go.jp/bosai/warning/data/warning/${areaCode}.json`;
let output = "";
try {
const data = await getData(url);
if (!data || !data.timeSeries) return;
const dateTime = new Date(data.reportDatetime);
const headlineText = `${dateTime.toLocaleString()} ${data.headlineText || ""}`;
const areas = data.timeSeries[0].areaTypes[1].areas;
const areaNum = areas.findIndex(a => a.code === cityCode);
if (areaNum === -1) return;
const warnings = areas[areaNum].warnings || [];
warnings.forEach(w => {
const alertCode = w.code;
const warningHTML = warningAlert(alertCode) || "";
output += `
<li id="alert${alertCode}" title="${headlineText}">
<a href="https://www.jma.go.jp/bosai/warning/#area_type=class20s&area_code=${cityCode}&lang=ja" target="_blank">
${warningHTML}
</a>
</li>`;
});
document.getElementById('weatherAlert').innerHTML = output;
} catch (err) {
console.error("警報取得エラー:", err);
document.getElementById('weatherAlert').innerHTML = "";
}
}
///////////////////////////////////////////////////////////////
// 地震情報 未使用中
// function earthApi() {
// const url = "https://api.p2pquake.net/v1/human-readable";
// getData(url).then((data) => {
// let output = "";
// let earthquake = data[0].earthquake;
// let time = earthquake.time;
// let maxScale = earthquake.maxScale / 10;
// let hypocenter = earthquake.hypocenter;
// let magnitude = hypocenter.magnitude;
// let name = hypocenter.name;
// let depth = hypocenter.depth;
// let blink = "";
// let blinkend = "";
// if (maxScale >= 4){
// blink = '<span class="blink">';
// blinkend = "</span>";
// }
// output += blink + '<a href="https://www.jma.go.jp/bosai/#pattern=earthquake_volcano&area_type=japan&area_code=010000" target="_blank" title="' + time + 'M' + magnitude + ' D' + depth + '">' + name + '[' + maxScale + ']</a>' + blinkend;
// document.getElementById('earthquake').innerHTML = output;
// });
// }
///////////////////////////////////////////////////////////
// 気象庁地震情報
// https://www.jma.go.jp/bosai/quake/data/list.json
function jmaEarthQuakeApi() {
const url ="https://www.jma.go.jp/bosai/quake/data/list.json";
getData(url).then((data) => {
// console.log(data)
let output,name,time,maxScale,magnitude,depth,blink,blinkend;
output = "";
let k=0;
// console.log(data)
for (let i = 0 ; i < 3 ; i++){
if (data[i].maxi == "" )
k = k+1;
maxScale = data[k].maxi;
name = data[k].anm;
time = data[k].at.replace(':00+09:00','');
magnitude = data[k].mag;
depth = data[k].cod.split(/-|\//,)[1]/1000;
blink = "";
blinkend = "";
let linkUrl = "https://www.jma.go.jp/bosai/#pattern=earthquake_volcano&area_type=japan&area_code=010000";
if ( i === 0 ){
linkUrl = "https://www.youtube.com/results?search_query=%E5%9C%B0%E9%9C%87%E9%80%9F%E5%A0%B1+%E3%83%A9%E3%82%A4%E3%83%96";
}
if (maxScale > 3){
blink = '<span class="blink">';
blinkend = "</span>";
}
output += `${blink}
<a href="${linkUrl}" target="_blank" title="${time}マグニチュード${magnitude} 深度${depth}Km">
${name}[${maxScale}]
</a>
${blinkend}
`
k = k+1;
// console.log(output)
}
document.getElementById('earthquake').innerHTML = output;
});
}
async function weatherSelectArea() {
const url = "https://www.jma.go.jp/bosai/common/const/area.json";
const data = await getData(url);
const offices = data.offices;
const areaSelect = document.getElementById("area");
const citySelect = document.getElementById("city");
// エリア選択肢作成
Object.keys(offices).forEach(v => {
const op = document.createElement("option");
op.value = v;
op.selected = (areaCode === v);
op.text = v.substring(0,2) + offices[v].name + offices[v].children;
areaSelect.appendChild(op);
});
// 市区町村セレクト更新
function cityChange() {
const selectedArea = areaSelect.value;
const selectedArea2 = selectedArea.substring(0,2);
const selectedText = areaSelect.options[areaSelect.selectedIndex].text;
// 既存 options クリア
citySelect.options.length = 0;
Object.keys(data.class20s).forEach(v => {
const class20 = data.class20s[v];
const city2 = v.substring(0,2);
const class10 = Number(class20.parent.substring(4,5)) - 1;
const class20Parent = Number(class20.parent.substring(0,5)) * 10;
// 特殊地域判定(北海道・沖縄・小笠原)
const isSpecial = [1, 46, 47].includes(Number(selectedArea2));
const includeCity = (city2 === selectedArea2) && (!isSpecial || selectedText.includes(class20Parent));
if (includeCity) {
const op = document.createElement("option");
op.value = `${class10}:${v}`;
op.selected = (cityCode === v);
op.text = `${class10}:${class20.name}${class20Parent}`;
citySelect.appendChild(op);
}
});
}
cityChange();
areaSelect.onchange = cityChange;
}
async function setupWeatherForm() {
const url = "https://www.jma.go.jp/bosai/common/const/area.json";
const data = await getData(url);
const areaSelect = document.getElementById("area");
const citySelect = document.getElementById("city");
const class10Select = document.getElementById("class10");
const form = areaSelect.form;
// エリア選択肢作成
Object.keys(data.offices).forEach(v => {
const op = document.createElement("option");
op.value = v;
op.selected = (areaCode === v);
op.text = v.substring(0,2) + data.offices[v].name + data.offices[v].children;
areaSelect.appendChild(op);
});
function updateCityOptions() {
const selectedArea = areaSelect.value;
const selectedArea2 = selectedArea.substring(0,2);
const selectedText = areaSelect.options[areaSelect.selectedIndex].text;
citySelect.options.length = 0;
Object.keys(data.class20s).forEach(v => {
const class20 = data.class20s[v];
const city2 = v.substring(0,2);
const class10 = Number(class20.parent.substring(4,5)) - 1;
const class20Parent = Number(class20.parent.substring(0,5)) * 10;
const isSpecial = [1,46,47].includes(Number(selectedArea2));
const includeCity = (city2 === selectedArea2) && (!isSpecial || selectedText.includes(class20Parent));
if(includeCity) {
const op = document.createElement("option");
op.value = `${class10}:${v}`;
op.selected = (cityCode === v);
op.text = `${class10}:${class20.name}${class20Parent}`;
citySelect.appendChild(op);
}
});
updateClass10Options();
}
function updateClass10Options() {
class10Select.options.length = 0;
const selectedCityValue = citySelect.value;
if(!selectedCityValue) return;
const [class10] = selectedCityValue.split(":");
const op = document.createElement("option");
op.value = class10;
op.text = class10;
class10Select.appendChild(op);
}
// 初期表示
updateCityOptions();
// イベント設定
areaSelect.onchange = () => {
updateCityOptions();
form.submit();
};
citySelect.onchange = () => {
updateClass10Options(); // city に応じて class10 更新
form.submit();
};
class10Select.onchange = () => form.submit();
}
/**
* class10/class20 の予報エリアに最適な Amedas 観測点コードを返す
* @param {string} class20Code - class20 市区町村コード
* @param {Array} amedasPoints - Amedas 観測点情報配列 [{code, lat, lon, name}, ...]
* @param {Object} areaCoords - 予報エリアの座標 {lat, lon}(任意で距離計算用)
* @returns {string} - 最適観測点コード
*/
function mapAreaToAmedas(class20Code, amedasPoints, areaCoords) {
// まず class20Code と完全一致する観測点を探す
let exactMatch = amedasPoints.find(p => p.areaCode === class20Code);
if (exactMatch) return exactMatch.code;
// 完全一致がなければ、最寄りの観測点を選択
if (areaCoords) {
// 緯度経度を使って最短距離の観測点を選ぶ
let nearest = amedasPoints.reduce((prev, curr) => {
const distPrev = Math.pow(prev.lat - areaCoords.lat,2) + Math.pow(prev.lon - areaCoords.lon,2);
const distCurr = Math.pow(curr.lat - areaCoords.lat,2) + Math.pow(curr.lon - areaCoords.lon,2);
return distCurr < distPrev ? curr : prev;
});
return nearest.code;
}
// どちらもなければ、とりあえず最初の観測点
return amedasPoints[0]?.code || "";
}
余談
最初に書き始めたのはタイムスタンプを見たら2018年頃w
これでJson覚えましたwww
コメント
コメントを投稿