각진 세상에 둥근 춤을 추자

[API] 3. 기상청 단기예보 조회 서비스 - 공공데이터포털 API 본문

프로젝트/공공데이터 포털 API

[API] 3. 기상청 단기예보 조회 서비스 - 공공데이터포털 API

circle.j 2024. 1. 17. 21:59


1. 공공데이터 포털 API 활용 신청 

https://www.data.go.kr/index.do

 

공공데이터 포털

국가에서 보유하고 있는 다양한 데이터를『공공데이터의 제공 및 이용 활성화에 관한 법률(제11956호)』에 따라 개방하여 국민들이 보다 쉽고 용이하게 공유•활용할 수 있도록 공공데이터(Datase

www.data.go.kr

아래 주소를 통해 회원가입 및 로그인 후 기상청_단기예보 API를 활용신청한다.

 

마이페이지의 활용신청 현황에서 일반 인증키(Encoding)를 복사한다. 

해당 화면에서 "상세설명"과 "참고문서"를 참고하여 코드를 작성한다. 

 

해당 화면의 "활용신청 상세기능정보"에서 요청변수에 따른 응답결과를 미리볼 수 있다. 

참고로 "초단기예보조회"는 최근 3일 간의 데이터만 제공하기 때문에 

base_date(발표시각) 요청 파라미터 값을 오늘 날짜로부터 3일 이내로 변경하여 요청한다. 

 

그럼 다음과 같은 JSON 타입의 결과값을 반환한다. 

 

해당 페이지의 "상세설명"을 통해 요청변수와 출력결과의 이름을 확인할 수 있다. 

 


2. JavaScript 코드 작성하기 

 

공공데이터포털 홈페이지에서 제공하는 JavaScript 샘플 코드는 다음과 같다.

'서비스키'에 일반 인증키(Encoding)를 붙여 넣는다. 

/* Javascript 샘플 코드 */


var xhr = new XMLHttpRequest();
var url = 'http://apis.data.go.kr/1360000/VilageFcstInfoService_2.0/getUltraSrtNcst'; /*URL*/
var queryParams = '?' + encodeURIComponent('serviceKey') + '='+'서비스키'; /*Service Key*/
queryParams += '&' + encodeURIComponent('pageNo') + '=' + encodeURIComponent('1'); /**/
queryParams += '&' + encodeURIComponent('numOfRows') + '=' + encodeURIComponent('1000'); /**/
queryParams += '&' + encodeURIComponent('dataType') + '=' + encodeURIComponent('JSON'); /**/
queryParams += '&' + encodeURIComponent('base_date') + '=' + encodeURIComponent('20210628'); /**/
queryParams += '&' + encodeURIComponent('base_time') + '=' + encodeURIComponent('0600'); /**/
queryParams += '&' + encodeURIComponent('nx') + '=' + encodeURIComponent('55'); /**/
queryParams += '&' + encodeURIComponent('ny') + '=' + encodeURIComponent('127'); /**/
xhr.open('GET', url + queryParams);
xhr.onreadystatechange = function () {
    if (this.readyState == 4) {
        alert('Status: '+this.status+'nHeaders: '+JSON.stringify(this.getAllResponseHeaders())+'nBody: '+this.responseText);
    }
};

xhr.send('');

 

아래 코드는 주소에 따른 위도, 경도 값을 통해 해당 위치의 현재 기상 정보를 조회한다. 

이때, 일단 위도, 경도 값이 아닌 기상 정보 API에서 요구하는 격자 x, y 포인트 값으로 조회한다. 

위도, 경도는 이전에 작성했던 카카오 지도의 geocoder에서 추출한다. 

let latPoint = "";  // 위도
let lonPoint = "";  // 경도
let xPoint = "";    // 격자 x 포인트
let yPoint = "";    // 격자 y 포인트

// 위도, 경도
let xValue = parseFloat(result[0].x);
let yValue = parseFloat(result[0].y);

latPoint = yValue;  // 위도
lonPoint = xValue;  // 경도

 

샘플 코드에 있는 요청 변수에 조회하고자 하는 값을 입력한다. 

base_date는 20240117 형식, base_time는 1730 형식, nx와 ny에는 변환된 x, y 포인트 값을 입력해야 한다. 

다만 base_time(발표 시각)은 30분 마다 갱신되기 때문에 현재 시각으로부터 30분 이전의 시각을 입력해야 한다. 

// 오늘 날짜 구하기
let currentDate = new Date();

let year = currentDate.getFullYear();
let month = ('0' + (currentDate.getMonth() + 1)).slice(-2);
let day = ('0' + currentDate.getDate()).slice(-2);

let formattedDate = year + month + day;

// 현재 시간에서 -30분 시간 구하기 (30분마다 데이터 갱신)
let hours = ('0' + currentDate.getHours()).slice(-2);
let minutes = ('0' + (currentDate.getMinutes() - 30)).slice(-2);

// 만약 minutes가 30보다 작으면 hours에서 1을 빼주기
if (currentDate.getMinutes() < 30) {
    hours = ('0' + (currentDate.getHours() - 1)).slice(-2);
    minutes = ('0' + (currentDate.getMinutes() + 30)).slice(-2);
}

let formattedTime = hours + minutes;

// 좌표를 격자 x,y point로 바꾸기
let result = dfs_xy_conv("toXY", latPoint, lonPoint);
console.log("X:", result.x, "Y:", result.y);

xPoint = result.x;
yPoint = result.y;

 

위도, 경도를 x, y 격자 포인트로 변경하는 JavaScript 코드 함수는 다음과 같다. 

/** 위도, 경도를 x,y 격자 포인트로 변경 */
var RE = 6371.00877; // 지구 반경(km)
var GRID = 5.0; // 격자 간격(km)
var SLAT1 = 30.0; // 투영 위도1(degree)
var SLAT2 = 60.0; // 투영 위도2(degree)
var OLON = 126.0; // 기준점 경도(degree)
var OLAT = 38.0; // 기준점 위도(degree)
var XO = 43; // 기준점 X좌표(GRID)
var YO = 136; // 기1준점 Y좌표(GRID)

// LCC DFS 좌표변환 ( code : "toXY"(위경도->좌표, v1:위도, v2:경도), "toLL"(좌표->위경도,v1:x, v2:y) )
function dfs_xy_conv(code, v1, v2) {
    // LCC DFS 좌표변환을 위한 기초 자료
    var DEGRAD = Math.PI / 180.0;
    var RADDEG = 180.0 / Math.PI;

    var re = RE / GRID;
    var slat1 = SLAT1 * DEGRAD;
    var slat2 = SLAT2 * DEGRAD;
    var olon = OLON * DEGRAD;
    var olat = OLAT * DEGRAD;

    var 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);
    var sf = Math.tan(Math.PI * 0.25 + slat1 * 0.5);
    sf = Math.pow(sf, sn) * Math.cos(slat1) / sn;
    var ro = Math.tan(Math.PI * 0.25 + olat * 0.5);
    ro = re * sf / Math.pow(ro, sn);
    var rs = {};
    if (code == "toXY") {
        rs['lat'] = v1;
        rs['lng'] = v2;
        var ra = Math.tan(Math.PI * 0.25 + (v1) * DEGRAD * 0.5);
        ra = re * sf / Math.pow(ra, sn);
        var theta = v2 * DEGRAD - olon;
        if (theta > Math.PI) theta -= 2.0 * Math.PI;
        if (theta < -Math.PI) theta += 2.0 * Math.PI;
        theta *= sn;
        rs['x'] = Math.floor(ra * Math.sin(theta) + XO + 0.5);
        rs['y'] = Math.floor(ro - ra * Math.cos(theta) + YO + 0.5);
    }
    else {
        rs['x'] = v1;
        rs['y'] = v2;
        var xn = v1 - XO;
        var yn = ro - v2 + YO;
        ra = Math.sqrt(xn * xn + yn * yn);
        if (sn < 0.0) - ra;
        var alat = Math.pow((re * sf / ra), (1.0 / sn));
        alat = 2.0 * Math.atan(alat) - Math.PI * 0.5;

        if (Math.abs(xn) <= 0.0) {
            theta = 0.0;
        }
        else {
            if (Math.abs(yn) <= 0.0) {
                theta = Math.PI * 0.5;
                if (xn < 0.0) - theta;
            }
            else theta = Math.atan2(xn, yn);
        }
        var alon = theta / sn + olon;
        rs['lat'] = alat * RADDEG;
        rs['lng'] = alon * RADDEG;
    }
    return rs;

}

 

준비된 요청 변수를 통해 현재 위치에 따른 기상 정보를 조회한다. 

var xhr = new XMLHttpRequest();
    var url = 'http://apis.data.go.kr/1360000/VilageFcstInfoService_2.0/getUltraSrtFcst'; /*URL*/
    var queryParams = '?' + encodeURIComponent('serviceKey') + '='+'app key'; /*Service Key*/
    queryParams += '&' + encodeURIComponent('pageNo') + '=' + encodeURIComponent('1'); /* 페이지 번호 */
    queryParams += '&' + encodeURIComponent('numOfRows') + '=' + encodeURIComponent('1000'); /* 한 페이지 결과 수 */
    queryParams += '&' + encodeURIComponent('dataType') + '=' + encodeURIComponent('JSON'); /* 응답자료형식 */
    queryParams += '&' + encodeURIComponent('base_date') + '=' + encodeURIComponent(formattedDate); /* 발표일자 */
    queryParams += '&' + encodeURIComponent('base_time') + '=' + encodeURIComponent(formattedTime); /* 발표시각 */
    queryParams += '&' + encodeURIComponent('nx') + '=' + encodeURIComponent(xPoint); /* 예보지점 X 좌표 */
    queryParams += '&' + encodeURIComponent('ny') + '=' + encodeURIComponent(yPoint); /* 예보지점 Y 좌표 */
    xhr.open('GET', url + queryParams);
    xhr.onreadystatechange = function () {
        if (this.readyState == 4) {
            if (this.status == 200) {
                console.log("Success!");

                // JSON 파싱
                var jsonResponse = JSON.parse(this.responseText);

                // 필요한 정보 추출
                var items = jsonResponse.response.body.items.item;

                console.log("items length: " + items.length);

                for (var i = 0; i < items.length; i++) {
                    var item = items[i];

                    console.log("item: " + JSON.stringify(item));
                    console.log("자료구분코드: " + item.category);
                    console.log("예보일자: " + item.fcstDate);
                    console.log("예보시각: " + item.fcstTime);
                    console.log("예보값: " + item.fcstValue);
                    
                }

            } else {
                console.log("Error: " + this.status);
            }
        }
    };

    xhr.send('');

 

예를 들어 콘솔창에 로그가 다음과 같이 뜬다.

해당 category값과 fcstValue 값을 활용한다.

코드값은 참고 문서를 통해 확인한다. 

 

위 로그의 경우, category "SKY"는 "하늘 상태" 코드를 나타내며 fcstValue "4"는 "흐림"을 나타낸다.