気象庁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

コメント

人気の投稿

Bloggerでは“コピー用アイコン”が出せないの?

Googleドライブの共有で浮気がバレるかもしれない・・・www

ブックマークレット