見出し画像

はじめてのJavaScript #29|天気情報を表示するアプリを作ろう

今回のゴール

無料の天気APIを使って、都市名を入力すると現在の天気が表示されるアプリを作る。


使用するAPI

今回は Open-Meteo という無料の天気APIを使います。

Open-Meteoを選んだ理由は3つあります。

  • 登録不要・APIキー不要で誰でも使える

  • 商用利用も無料で制限が少ない

  • 日本語対応の地名検索と組み合わせて使える

ただし天気データの取得には「緯度・経度」が必要です。
そこで都市名から緯度・経度を取得する Geocoding API(同じくOpen-Meteo提供)も合わせて使います。

① 都市名 → Geocoding API → 緯度・経度
② 緯度・経度 → Weather API → 天気データ

APIのURL構造を確認しよう

① 都市名から緯度・経度を取得する

https://geocoding-api.open-meteo.com/v1/search?name=Tokyo&count=1&language=ja

② 緯度・経度から天気を取得する

https://api.open-meteo.com/v1/forecast
  ?latitude=35.68
  &longitude=139.69
  &current=temperature_2m,weathercode,windspeed_10m
  &timezone=Asia/Tokyo

current に取得したいデータを指定します。今回は気温・天気コード・風速を取得します。

天気コードを天気の説明に変換する

Open-Meteoは天気を数字の「天気コード」で返してきます。
そのままでは意味がわからないので、コードに対応する説明のオブジェクトを用意します。

js

const weatherDescriptions = {
  0:  "快晴",
  1:  "ほぼ快晴",
  2:  "一部曇り",
  3:  "曇り",
  45: "霧",
  48: "霧氷",
  51: "霧雨(弱)",
  53: "霧雨",
  55: "霧雨(強)",
  61: "雨(弱)",
  63: "雨",
  65: "雨(強)",
  71: "雪(弱)",
  73: "雪",
  75: "雪(強)",
  80: "にわか雨(弱)",
  81: "にわか雨",
  82: "にわか雨(強)",
  95: "雷雨",
  99: "雷雨と雹"
};

実際に書いてみよう

index.html

html

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8">
    <title>天気アプリ</title>
  </head>
  <body>
    <h1>天気情報アプリ</h1>

    <input id="cityInput" type="text" placeholder="都市名を入力(例:Tokyo)">
    <button id="btnSearch">検索</button>

    <p id="status"></p>

    <div id="result" style="display:none;">
      <h2 id="cityName"></h2>
      <p id="weather"></p>
      <p id="temperature"></p>
      <p id="windspeed"></p>
    </div>

    <script src="script.js"></script>
  </body>
</html>

script.js

js

// 天気コードの説明
const weatherDescriptions = {
  0:  "快晴",
  1:  "ほぼ快晴",
  2:  "一部曇り",
  3:  "曇り",
  45: "霧",
  48: "霧氷",
  51: "霧雨(弱)",
  53: "霧雨",
  55: "霧雨(強)",
  61: "雨(弱)",
  63: "雨",
  65: "雨(強)",
  71: "雪(弱)",
  73: "雪",
  75: "雪(強)",
  80: "にわか雨(弱)",
  81: "にわか雨",
  82: "にわか雨(強)",
  95: "雷雨",
  99: "雷雨と雹"
};

// 要素の取得
const cityInput     = document.getElementById("cityInput");
const btnSearch     = document.getElementById("btnSearch");
const statusEl      = document.getElementById("status");
const resultEl      = document.getElementById("result");
const cityNameEl    = document.getElementById("cityName");
const weatherEl     = document.getElementById("weather");
const temperatureEl = document.getElementById("temperature");
const windspeedEl   = document.getElementById("windspeed");

// 都市名から緯度・経度を取得する関数
async function getCoordinates(cityName) {
  const url =
    "https://geocoding-api.open-meteo.com/v1/search?name=" +
    encodeURIComponent(cityName) +
    "&count=1&language=ja";

  const response = await fetch(url);
  if (!response.ok) throw new Error("都市情報の取得に失敗しました");

  const data = await response.json();
  if (!data.results || data.results.length === 0) {
    throw new Error("都市が見つかりませんでした:" + cityName);
  }

  return {
    name:      data.results[0].name,
    latitude:  data.results[0].latitude,
    longitude: data.results[0].longitude
  };
}

// 緯度・経度から天気を取得する関数
async function getWeather(latitude, longitude) {
  const url =
    "https://api.open-meteo.com/v1/forecast" +
    "?latitude=" + latitude +
    "&longitude=" + longitude +
    "&current=temperature_2m,weathercode,windspeed_10m" +
    "&timezone=Asia%2FTokyo";

  const response = await fetch(url);
  if (!response.ok) throw new Error("天気情報の取得に失敗しました");

  const data = await response.json();
  return data.current;
}

// メインの処理
async function searchWeather() {
  const cityName = cityInput.value.trim();

  if (!cityName) {
    statusEl.textContent = "都市名を入力してください。";
    statusEl.style.color = "red";
    return;
  }

  // ボタンを無効化
  btnSearch.disabled     = true;
  statusEl.textContent   = "取得中...";
  statusEl.style.color   = "gray";
  resultEl.style.display = "none";

  try {
    // ① 緯度・経度を取得
    const location = await getCoordinates(cityName);

    // ② 天気を取得
    const weather = await getWeather(location.latitude, location.longitude);

    // ③ 天気コードを説明に変換
    const description =
      weatherDescriptions[weather.weathercode] || "不明";

    // ④ 画面に表示
    statusEl.textContent   = "取得完了!";
    statusEl.style.color   = "green";
    resultEl.style.display = "block";

    cityNameEl.textContent    = location.name + " の現在の天気";
    weatherEl.textContent     = "天気:" + description;
    temperatureEl.textContent = "気温:" + weather.temperature_2m + "℃";
    windspeedEl.textContent   = "風速:" + weather.windspeed_10m + " m/s";

  } catch (error) {
    statusEl.textContent   = "エラー:" + error.message;
    statusEl.style.color   = "red";
    resultEl.style.display = "none";

  } finally {
    btnSearch.disabled = false;
  }
}

// ボタンのイベント
btnSearch.addEventListener("click", function() {
  searchWeather();
});

// Enterキーでも検索できるようにする
cityInput.addEventListener("keydown", function(e) {
  if (e.key === "Enter") searchWeather();
});

入力欄に Tokyo や Osaka Sapporo などの都市名を入力して検索してみましょう。リアルな天気データが表示されます。

コードのポイント解説

処理を関数に分ける

今回は「緯度・経度の取得」と「天気の取得」を別々の関数に分けました。

js

const location = await getCoordinates(cityName);
const weather  = await getWeather(location.latitude, location.longitude);

1つの関数に全部まとめることもできますが、役割ごとに分けることでコードが読みやすくなり、どこでエラーが起きたかも把握しやすくなります。

encodeURIComponent() で都市名を安全に渡す

URLに日本語や特殊文字が含まれると正しく動かないことがあります。encodeURIComponent() を使うとURLで安全に扱える形式に変換してくれます。

js

encodeURIComponent("大阪") // → "%E5%A4%A7%E9%98%AA"

Enterキーでも検索できるようにする

ユーザビリティの向上として、ボタンクリックだけでなくEnterキーでも検索できるようにしています。

js

cityInput.addEventListener("keydown", function(e) {
  if (e.key === "Enter") searchWeather();
});

まとめ

  • 2つのAPIを順番に呼び出す場合は await を使って直列に処理する

  • 役割ごとに関数を分けるとコードが読みやすくなる

  • URLに特殊文字を含める場合は encodeURIComponent() で変換する

次回予告

天気アプリが完成しました。次回はAPIから返ってくるJSONの構造をより深く理解します。

→ #30|JSONってなに? ── データのやりとりを理解する


いいなと思ったら応援しよう!

DOI@日々学びの本質を考え続ける探求者 よろしければ応援お願いします! いただいたチップは引き続きプログラミングや学びについて、皆さんの利益になるようなよい記事を書くことで恩返しをさせていただきます!