본문 바로가기

IT와 과학/API

기상청 API (날씨, 기후 데이터) 완전 정복

728x90
반응형

기상청 API (날씨, 기후 데이터) 완전 정복

개요

기상청에서 제공하는 공공데이터 API는 대한민국의 기상 정보를 실시간으로 제공하는 핵심 서비스입니다. 날씨 앱, 농업 서비스, 관광 플랫폼 등 다양한 분야에서 활용할 수 있는 풍부한 기상 데이터를 제공합니다.

주요 API 서비스 종류

1. 단기예보 조회서비스

  • 용도: 3일간의 상세 날씨 예보
  • 제공 정보: 기온, 습도, 강수확률, 풍향, 풍속 등
  • 갱신 주기: 하루 8회 (02, 05, 08, 11, 14, 17, 20, 23시)

2. 중기예보 조회서비스

  • 용도: 4일~10일 후 날씨 전망
  • 제공 정보: 날씨 상태, 최고/최저 기온
  • 갱신 주기: 하루 2회 (06, 18시)

3. 실시간 관측정보

  • 용도: 현재 기상 상황
  • 제공 정보: 현재 기온, 습도, 강수량, 바람 정보
  • 갱신 주기: 10분마다

4. 특보 조회서비스

  • 용도: 기상특보, 태풍정보, 지진정보
  • 제공 정보: 특보 발표/해제 상태, 특보 내용
  • 갱신 주기: 실시간

5. 생활기상지수

  • 용도: 일상생활과 관련된 기상지수
  • 제공 정보: 자외선지수, 불쾌지수, 체감온도, 식중독지수 등
  • 갱신 주기: 일 1회

API 사용 준비하기

1단계: 서비스 키 발급

  1. 공공데이터포털 접속
  2. 회원가입 및 로그인
  3. "기상청_단기예보 조회서비스" 검색 후 활용신청
  4. 승인 후 서비스키 확인 (마이페이지 > 개발계정 상세보기)

2단계: 좌표계 이해

기상청 API는 기상청 격자 좌표계를 사용합니다.

  • 일반적인 위경도 좌표(WGS84)를 기상청 격자 좌표(X, Y)로 변환 필요
  • 또는 기상청에서 제공하는 지역별 격자 좌표표 활용

핵심 API 상세 가이드

단기예보 조회 (가장 많이 사용)

API 엔드포인트:

https://apis.data.go.kr/1360000/VilageFcstInfoService_2.0/getVilageFcst

필수 파라미터:

  • serviceKey: 발급받은 API 인증키
  • numOfRows: 한 페이지 결과 수 (기본값: 10)
  • pageNo: 페이지 번호 (기본값: 1)
  • dataType: 응답 자료형식 (XML/JSON)
  • base_date: 발표일자 (YYYYMMDD 형식)
  • base_time: 발표시각 (HHMM 형식)
  • nx: 예보지점 X 좌표
  • ny: 예보지점 Y 좌표

호출 예시:

const API_KEY = 'your_service_key_here';
const base_date = '20231215';
const base_time = '1100';
const nx = 60; // 서울시 중구 격자 X좌표
const ny = 127; // 서울시 중구 격자 Y좌표

const url = `https://apis.data.go.kr/1360000/VilageFcstInfoService_2.0/getVilageFcst?serviceKey=${API_KEY}&numOfRows=1000&pageNo=1&dataType=JSON&base_date=${base_date}&base_time=${base_time}&nx=${nx}&ny=${ny}`;

fetch(url)
  .then(response => response.json())
  .then(data => {
    console.log('날씨 데이터:', data.response.body.items.item);
  })
  .catch(error => {
    console.error('API 호출 오류:', error);
  });

응답 데이터 이해하기

기상청 API는 다음과 같은 카테고리 코드로 데이터를 제공합니다:

카테고리 설명 단위

TMP 1시간 기온
UUU 풍속(동서성분) m/s
VVV 풍속(남북성분) m/s
VEC 풍향 deg
WSD 풍속 m/s
SKY 하늘상태 코드값
PTY 강수형태 코드값
POP 강수확률 %
WAV 파고 M
PCP 1시간 강수량 mm
REH 습도 %
SNO 1시간 신적설 cm

하늘상태(SKY) 코드:

  • 1: 맑음
  • 3: 구름많음
  • 4: 흐림

강수형태(PTY) 코드:

  • 0: 없음
  • 1: 비
  • 2: 비/눈
  • 3: 눈
  • 4: 소나기

실용적인 데이터 파싱 예제

function parseWeatherData(apiResponse) {
  const items = apiResponse.response.body.items.item;
  const weatherData = {};
  
  // 시간대별로 데이터 그룹핑
  items.forEach(item => {
    const dateTime = `${item.fcstDate}_${item.fcstTime}`;
    
    if (!weatherData[dateTime]) {
      weatherData[dateTime] = {};
    }
    
    weatherData[dateTime][item.category] = item.fcstValue;
  });
  
  // 사용자 친화적 형태로 변환
  const result = Object.keys(weatherData).map(dateTime => {
    const data = weatherData[dateTime];
    
    return {
      date: dateTime.split('_')[0],
      time: dateTime.split('_')[1],
      temperature: data.TMP ? `${data.TMP}℃` : null,
      humidity: data.REH ? `${data.REH}%` : null,
      windSpeed: data.WSD ? `${data.WSD}m/s` : null,
      precipitation: data.PCP ? data.PCP : '없음',
      precipitationProbability: data.POP ? `${data.POP}%` : null,
      skyCondition: getSkyCondition(data.SKY),
      precipitationType: getPrecipitationType(data.PTY)
    };
  });
  
  return result;
}

function getSkyCondition(skyCode) {
  const conditions = {
    '1': '맑음',
    '3': '구름많음',
    '4': '흐림'
  };
  return conditions[skyCode] || '알 수 없음';
}

function getPrecipitationType(ptyCode) {
  const types = {
    '0': '없음',
    '1': '비',
    '2': '비/눈',
    '3': '눈',
    '4': '소나기'
  };
  return types[ptyCode] || '알 수 없음';
}

좌표 변환 가이드

위경도를 기상청 격자 좌표로 변환

function latLonToGrid(lat, lon) {
  const RE = 6371.00877; // 지구 반경(km)
  const GRID = 5.0; // 격자 간격(km)
  const SLAT1 = 30.0; // 투영 위도1(degree)
  const SLAT2 = 60.0; // 투영 위도2(degree)
  const OLON = 126.0; // 기준점 경도(degree)
  const OLAT = 38.0; // 기준점 위도(degree)
  const XO = 43; // 기준점 X좌표(GRID)
  const YO = 136; // 기준점 Y좌표(GRID)
  
  const DEGRAD = Math.PI / 180.0;
  const re = RE / GRID;
  const slat1 = SLAT1 * DEGRAD;
  const slat2 = SLAT2 * DEGRAD;
  const olon = OLON * DEGRAD;
  const olat = OLAT * DEGRAD;
  
  let sn = Math.tan(Math.PI * 0.25 + slat2 * 0.5) / Math.tan(Math.PI * 0.25 + slat1 * 0.5);
  sn = Math.log(Math.cos(slat1) / Math.cos(slat2)) / Math.log(sn);
  let sf = Math.tan(Math.PI * 0.25 + slat1 * 0.5);
  sf = Math.pow(sf, sn) * Math.cos(slat1) / sn;
  let ro = Math.tan(Math.PI * 0.25 + olat * 0.5);
  ro = re * sf / Math.pow(ro, sn);
  
  let ra = Math.tan(Math.PI * 0.25 + lat * DEGRAD * 0.5);
  ra = re * sf / Math.pow(ra, sn);
  let theta = lon * DEGRAD - olon;
  if (theta > Math.PI) theta -= 2.0 * Math.PI;
  if (theta < -Math.PI) theta += 2.0 * Math.PI;
  theta *= sn;
  
  const x = Math.floor(ra * Math.sin(theta) + XO + 0.5);
  const y = Math.floor(ro - ra * Math.cos(theta) + YO + 0.5);
  
  return { x, y };
}

// 사용 예시
const 서울좌표 = latLonToGrid(37.5665, 126.9780);
console.log(서울좌표); // {x: 60, y: 127}

실제 프로젝트 구현 예제

간단한 날씨 조회 함수

class WeatherAPI {
  constructor(serviceKey) {
    this.serviceKey = serviceKey;
    this.baseUrl = 'https://apis.data.go.kr/1360000/VilageFcstInfoService_2.0';
  }
  
  // 현재 시각 기준으로 가장 최신 예보 시각 계산
  getLatestForecastTime() {
    const now = new Date();
    const currentHour = now.getHours();
    
    // 기상청 예보 발표 시간: 02, 05, 08, 11, 14, 17, 20, 23시
    const forecastTimes = ['0200', '0500', '0800', '1100', '1400', '1700', '2000', '2300'];
    
    let latestTime = '2300';
    let baseDate = now;
    
    for (let i = 0; i < forecastTimes.length; i++) {
      const forecastHour = parseInt(forecastTimes[i].substring(0, 2));
      if (currentHour >= forecastHour) {
        latestTime = forecastTimes[i];
      } else {
        break;
      }
    }
    
    // 만약 현재 시간이 02시 이전이면 전날 23시 예보 사용
    if (currentHour < 2) {
      baseDate.setDate(baseDate.getDate() - 1);
      latestTime = '2300';
    }
    
    const baseDateStr = baseDate.getFullYear().toString() +
                       (baseDate.getMonth() + 1).toString().padStart(2, '0') +
                       baseDate.getDate().toString().padStart(2, '0');
    
    return { base_date: baseDateStr, base_time: latestTime };
  }
  
  async getWeather(lat, lon) {
    try {
      // 위경도를 격자 좌표로 변환
      const grid = latLonToGrid(lat, lon);
      const { base_date, base_time } = this.getLatestForecastTime();
      
      const url = `${this.baseUrl}/getVilageFcst?` +
                  `serviceKey=${this.serviceKey}&` +
                  `numOfRows=1000&pageNo=1&dataType=JSON&` +
                  `base_date=${base_date}&base_time=${base_time}&` +
                  `nx=${grid.x}&ny=${grid.y}`;
      
      const response = await fetch(url);
      const data = await response.json();
      
      if (data.response.header.resultCode !== '00') {
        throw new Error(`API 오류: ${data.response.header.resultMsg}`);
      }
      
      return parseWeatherData(data);
      
    } catch (error) {
      console.error('날씨 정보 조회 실패:', error);
      throw error;
    }
  }
}

// 사용법
const weatherAPI = new WeatherAPI('your_service_key');

weatherAPI.getWeather(37.5665, 126.9780) // 서울시청 좌표
  .then(weather => {
    console.log('오늘 날씨:', weather[0]);
  })
  .catch(error => {
    console.error('오류:', error);
  });

자주 발생하는 오류와 해결책

1. INVALID_REQUEST_PARAMETER_ERROR

원인: 필수 파라미터 누락 또는 형식 오류 해결:

  • base_date, base_time 형식 확인 (YYYYMMDD, HHMM)
  • nx, ny 좌표값이 정수인지 확인

2. SERVICE_ACCESS_DENIED_ERROR

원인: 잘못된 서비스키 또는 미승인 상태 해결:

  • 공공데이터포털에서 서비스 이용승인 상태 확인
  • 서비스키 복사 시 공백 포함 여부 확인

3. SERVICE_TIME_OUT_ERROR

원인: API 서버 응답 지연 해결:

  • 재시도 로직 구현
  • 요청 간격 조절 (초당 1000건 제한)

고급 활용 팁

1. 캐싱 전략

기상 데이터는 주기적으로 갱신되므로 효율적인 캐싱이 중요합니다.

class WeatherCache {
  constructor(ttl = 600000) { // 10분 캐시
    this.cache = new Map();
    this.ttl = ttl;
  }
  
  getKey(lat, lon) {
    return `${lat.toFixed(4)}_${lon.toFixed(4)}`;
  }
  
  get(lat, lon) {
    const key = this.getKey(lat, lon);
    const cached = this.cache.get(key);
    
    if (cached && Date.now() - cached.timestamp < this.ttl) {
      return cached.data;
    }
    
    return null;
  }
  
  set(lat, lon, data) {
    const key = this.getKey(lat, lon);
    this.cache.set(key, {
      data: data,
      timestamp: Date.now()
    });
  }
}

2. 여러 지역 동시 조회

async function getMultipleWeather(locations, weatherAPI) {
  const promises = locations.map(location => 
    weatherAPI.getWeather(location.lat, location.lon)
      .then(weather => ({ ...location, weather }))
      .catch(error => ({ ...location, error: error.message }))
  );
  
  return Promise.all(promises);
}

3. 실시간 알림 시스템

class WeatherAlert {
  constructor(weatherAPI) {
    this.weatherAPI = weatherAPI;
    this.alerts = [];
  }
  
  addAlert(lat, lon, condition, callback) {
    this.alerts.push({ lat, lon, condition, callback });
  }
  
  async checkAlerts() {
    for (const alert of this.alerts) {
      try {
        const weather = await this.weatherAPI.getWeather(alert.lat, alert.lon);
        const currentWeather = weather[0];
        
        if (alert.condition(currentWeather)) {
          alert.callback(currentWeather);
        }
      } catch (error) {
        console.error('알림 체크 오류:', error);
      }
    }
  }
  
  startMonitoring(interval = 600000) { // 10분마다 체크
    setInterval(() => this.checkAlerts(), interval);
  }
}

// 사용 예시
const alertSystem = new WeatherAlert(weatherAPI);

// 비가 올 때 알림
alertSystem.addAlert(37.5665, 126.9780, 
  (weather) => weather.precipitationType !== '없음',
  (weather) => console.log('비가 옵니다!', weather)
);

결론

기상청 API는 정확하고 실시간성이 뛰어난 기상 데이터를 제공하는 강력한 도구입니다. 좌표 변환과 데이터 파싱만 잘 이해하면 다양한 날씨 관련 서비스를 구축할 수 있습니다.

다음 글에서는 국토교통부 API를 통한 교통 및 부동산 정보 활용법에 대해 알아보겠습니다!

참고 자료

728x90
반응형