기상청 공공 API 쓰는 법

  1. 행정안전부 공공데이터 포털 접속
    https://www.data.go.kr/
  2. 기상청 단기예보 조회서비스 검색, 오픈 API 목록에 있다
    https://www.data.go.kr/data/15084084/openapi.do
  3. 활용 신청 및 승인
    1. 로그인 / 회원가입
    2. API 활용 사유만 쓰면 활용신청 가능, 조금 기다리면 바로 승인됨
  4. 엔드 포인트와 인증키 메모
  5. API 호출 함수 생성
    fetchWeather() 함수를 만들었다
  6. 날씨 데이터를 표시할 모달 컴포넌트 생성
  7. 아이콘 클릭시 API 호출 및 모달 표시

현재 위치에 따라 날씨 데이터 가져오기

아래는 내 프로젝트에서 사용된 WeatherModal 컴포넌트 코드로 리액트, 타입스크립트, 테일윈드를 사용하고 있다

주석에 설명을 추가하였다

import React, { useEffect, useState } from "react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
  faTimes,
  faMapMarkerAlt,
  faThermometerHalf,
  faTint,
  faWind,
} from "@fortawesome/free-solid-svg-icons";

interface WeatherModalProps {
  isOpen: boolean;
  onClose: () => void;
}

const WeatherModal: React.FC<WeatherModalProps> = ({ isOpen, onClose }) => {
  const [weatherData, setWeatherData] = useState<any>(null);
  const [isLoading, setIsLoading] = useState<boolean>(true);
  const [error, setError] = useState<string | null>(null);
  const [location, setLocation] = useState<{ nx: number; ny: number } | null>(
    null
  );

  useEffect(() => {
    if (isOpen) {
      navigator.geolocation.getCurrentPosition(
        (position) => {
          const { latitude, longitude } = position.coords;
          const { nx, ny } = convertToGridCoords(latitude, longitude);
          setLocation({ nx, ny });
        },
        (error) => {
          console.error("Error getting location:", error);
          setError("위치를 가져올 수 없습니다.");
        }
      );
    }
  }, [isOpen]);

  useEffect(() => {
    if (isOpen && location) {
      fetchWeather(location.nx, location.ny);
    }
  }, [isOpen, location]);

  const fetchWeather = async (nx: number, ny: number) => {
    setIsLoading(true);
    setError(null);
    try {
      const apiKey =
        "vk4aWLTlPOiSCrGPCqW9+RPDwCEbd2qZroyXnqkjhfKAQM15ca8W2DHua28iBIE0OaNWNEPaaAR2suzllAcotg==";
      const encodedKey = encodeURIComponent(apiKey);

      const today = new Date(); // 오늘의 날짜 객체 생성
      const hour = today.getHours(); // 현재 시각 불러오기

      let formattedTime = hour.toString().padStart(2, "0") + "00"; // API에서 요구하는 HH00 형식으로 변환

      const formattedDate = today.toISOString().slice(0, 10).replace(/-/g, "");

      const baseUrl =
        "https://apis.data.go.kr/1360000/VilageFcstInfoService_2.0/getUltraSrtNcst";
      const queryParams = `numOfRows=10&pageNo=1&base_date=${formattedDate}&base_time=${formattedTime}&nx=${nx}&ny=${ny}&dataType=JSON`;

      const url = `${baseUrl}?serviceKey=${encodedKey}&${queryParams}`;

      const response = await fetch(url);
      const data = await response.json();

      if (data.response && data.response.body && data.response.body.items) {
        const items = data.response.body.items.item;
        const processedData = processWeatherData(items);
        setWeatherData(processedData);
      } else {
        throw new Error("예상치 못한 API 응답 구조");
      }
    } catch (error) {
      console.error("날씨 데이터를 가져오는 중 오류 발생:", error);
      setError(
        error instanceof Error
          ? error.message
          : "알 수 없는 오류가 발생했습니다."
      );
    } finally {
      setIsLoading(false);
    }
  };

  const processWeatherData = (items: any[]) => {
    const processed: any = {};
    items.forEach((item) => {
      switch (item.category) {
        case "T1H": // 기온
          processed.temperature = parseFloat(item.obsrValue);
          break;
        case "RN1": // 강수량
          processed.rainfall = parseFloat(item.obsrValue);
          break;
        case "REH": // 습도
          processed.humidity = parseFloat(item.obsrValue);
          break;
        case "WSD": // 풍속
          processed.windSpeed = parseFloat(item.obsrValue);
          break;
      }
    });
    return processed;
  };

	// 위치 변환 함수 : 사용자의 현재 위치(위도 및 경도)를 기상청 API가 요구하는 격자 좌표(nx, ny)로 변환하는 함수
  const convertToGridCoords = (lat: number, lon: number) => {
    return { nx: 55, ny: 127 }; // 격자 좌표로 변환하는 예시 좌표 반환
  };

  if (!isOpen) {
    return null;
  }

  return (
    <div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
      <div className="bg-white p-6 rounded-lg max-w-sm w-full">
        <div className="flex justify-between items-center">
          <h2 className="text-xl font-bold">현재 날씨</h2>
          <button
            onClick={onClose}
            className="text-gray-500 hover:text-gray-700"
          >
            <FontAwesomeIcon icon={faTimes} />
          </button>
        </div>
        <FontAwesomeIcon icon={faMapMarkerAlt} className="mr-2" />
        <span>현재 위치</span>
        {isLoading ? (
          <p>날씨 데이터를 불러오는 중...</p>
        ) : error ? (
          <p className="text-red-500">오류: {error}</p>
        ) : weatherData ? (
          <div>
            <div className="flex justify-between gap-2 mt-2">
              <div className="flex-grow basis-0 bg-blue-100 p-3 rounded-lg">
                <FontAwesomeIcon
                  icon={faThermometerHalf}
                  className="mr-2 text-blue-500"
                />
                <span className="font-bold">{weatherData.temperature}°C</span>
              </div>
              <div className="flex-grow basis-0 bg-blue-100 p-3 rounded-lg">
                <FontAwesomeIcon icon={faTint} className="mr-2 text-blue-500" />
                <span className="font-bold">{weatherData.humidity}%</span>
              </div>
              <div className="flex-grow basis-0 bg-blue-100 p-3 rounded-lg">
                <FontAwesomeIcon icon={faWind} className="mr-2 text-blue-500" />
                <span className="font-bold">{weatherData.windSpeed}m/s</span>
              </div>
              {weatherData.rainfall > 0 && (
                <div className="bg-blue-100 p-3 rounded-lg">
                  <FontAwesomeIcon
                    icon={faTint}
                    className="mr-2 text-blue-500"
                  />
                  <span className="font-bold">{weatherData.rainfall} mm</span>
                </div>
              )}
            </div>
          </div>
        ) : (
          <p>날씨 데이터가 없습니다.</p>
        )}
      </div>
    </div>
  );
};

export default WeatherModal;

근데 이렇게 하면 날씨가 개떡같이 나올 것이다

현재 위치를 00시 00구 00동으로 나타내려면 역지오코딩(Reverse Geocoding)이라는 기술을 사용해야한다

그건 다음 포스팅에서...

+ Recent posts