[leaflet] 지도 위도/경도로 기상청 api 불러오기

Yuni·2023년 6월 16일
4

지도

목록 보기
10/12
post-thumbnail

이전에 위도/경도로 주소를 불러오는 것을 해보았는데
이번에는 위도/경도로 기상청 api를 불러와서 연결해보려고 한다

우선 준비물은 api를 가져오는 것!
기상청단기예보 ((구)동네예보) 조회서비스 <-- 요기서 확인가능!

로그인하고 활용신청을 하고 apikey를 받은 뒤에 사용해주면된다
참고자료는 필요한 코드를 볼수 있으니 꼭 받아서 보길바란다!

초단기예보 << 나는 이것을 이용하였다

홈페이지 참고문서쪽에 보면 위도/경도를 가져와서 띄우는게아니라
gridx,gridy --> x,y 좌표를 통해 가져오기 때문에 변환을 해준후에 가져와야한다

문서에는 c언어만 변환 방법을 알려줬는데 여기가면 친절한 분들이 올려두셧다
언어별로 종류가 엄청 많으니 꼭 java, javascript가 아닌분들은 참고해도 좋을 것 같다
기상청 x,y 변환방법 <-- 요기서 확인가능!

기상청 사이트에서도 확인을 해볼 수 있는데
기상청 날씨누리 들어가게되면 페이지 소스 보기를 한 후에
app-weather-forecast-short-term.js <-- 요기서 확인가능!
여기 맨 아래를 보면 javascript를 이용하여 변환한 방법이 올라와있다

이것도 2가지 방법으로 해보았는데
javascript는 전체적으로 다올렸고
java로 하는 부분은 그냥 데이터만 들어왔는지 확인하는거까지 진행하였다

1. javascript

grid.js

/*
*   위도경도 좌표 변환
*/
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) {
    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;
}

api_forecast.jsp

var nConytol;
/*
* 날씨 변수
*/
const sunny = "1"; // 맑음
const many_cloudy = "3"; // 구름많음
const cloudy = "4"; // 흐림

const none = "0"; // 없음
const rain = "1"; // 비
const snow_rain = "2"; // 비/눈
const snow = "3"; // 눈
const shower = "4"; // 소나기
const raindrop = "5"; // 빗방울
const raindrop_blowingsnow = "6"; // 빗방울/눈날림
const blowingsnow = "7"; // 눈날림
/*
*  날짜 변수
*/
var now = new Date();

var year = now.getFullYear();
var month = ('0' + (now.getMonth() + 1)).slice(-2);
var day = ('0' + now.getDate()).slice(-2);
var dateString = year+ month + day;
/*
* 시간 변수
*/
var hours = ('0' + now.getHours()).slice(-2);
var minutes = ('0' + now.getMinutes()).slice(-2);
var seconds = ('0' +now.getSeconds()).slice(-2);
/*
* 30분 무조건 붙이기
*/
function time(){
// console.log(minutes);
	if (minutes > "30"){
		return hours + "30";
	}else {
		var getHoursTime = now.getHours() - 1;
		var setHoursTime = ('0' + getHoursTime).slice(-2);
		return  setHoursTime +"30";
	}
}
/*
* 정시로만 만들기
*/
function fixed_time(){
	if (minutes > "30"){
		return now.getHours() + 1;
	}else {
		return hours;
	}
}
/*
* 동네 예보
*/
function api_forecast() {
  mymap.on('mousedown', function(e) {
    // 여기서 위도경도를 좌표로 바꿔주도록 한다
  	var grid = dfs_xy_conv("toXY",e.latlng.lat,e.latlng.lng);
    var url = new URL("http://apis.data.go.kr/1360000/VilageFcstInfoService_2.0/getUltraSrtFcst");
    var params = {
      serviceKey: '${key_weather}',
      pageNo: 1,
      numOfRows: 60, // 10개의 카테고리에서 6개씩 나옴
      dataType: 'json',
      base_date: dateString,
      base_time: time(),
                      // 변환된 정보를 넣어준다
      nx: grid.x,
      ny: grid.y
    };
    url.search = new URLSearchParams(params).toString();

    fetch(decodeURI(url)) // url 입력, GET메서드임
      .then(res => {
      // console.log(res);
      // response 처리
      // 응답을 JSON 형태로 파싱
      return res.json();
    })
      .then(data => {
      weather_data(data);
      //console.log(data);
    })
      .catch(err => {
      // error 처리
      console.log('Fetch Error', err);
    });
  })
}

이제 data를 받아서 기상정보를 이쁘게 정리?? 해보도록한다
홈페이지에 들어가면 기상정보를 줄때 어떤식으로 주는지에 대해 볼수있는데

function weather_data(data){
            // console.log(data)
  var index = 0;
  var index1 = 0;

  if (data.response.header.resultMsg == '파라미터가 잘못되엇습니다.'){ // 국내 외에 다른지역을 찍을경우 오류코드
    $('#weatherData_table').hide(); //숨기기

  }else {
    let len = data.response.body.items.item.length;

    for( let i=0; i<len; i++ ) {
      const weatherData = data.response.body.items.item[i];

      if (weatherData.category== "T1H" || weatherData.category=="SKY"|| weatherData.category=="PTY") {
        var time_weather = weatherData.fcstTime.slice(0, -2);
        var t1h =''; // 기온
        var sky = ''; // 하늘
        var pty = ''; // 강수형태

        switch (weatherData.category){
          case "T1H": // 기온
            t1h = weatherData.fcstValue + "℃";
            break;
          case "SKY": // 하늘
            switch (weatherData.fcstValue) {
              case sunny: // 맑음
                if ("06" < time_weather < "18") {
                  sky = "<a><img alt='맑음' src='/images/sunny.png' height='45px' title='맑음'/></a>";
                } else {
                  sky = "<a><img alt='맑음_저녁' src='/images/clear_night.png' height='45px' title='맑음'/></a>";
                }
                break;
              case many_cloudy: // 구름많음
                if ("06" < time_weather < "18") {
                  sky = "<a><img alt='구름많음' src='/images/many+cloudy.png' height='45px' title='구름많음'/></a>";
                } else {
                  sky = "<a><img alt='구름많음_저녁' src='/images/many+cloudy_night.png' height='45px' title='구름많음'></a>";
                }
                break;
              case cloudy: // 흐림
                if ("06" < time_weather < "18") {
                  sky = "<a><img alt='흐림' src='/images/cloudy.png' height='45px' title='흐림'/></a>";
                } else {
                  sky = "<a><img alt='흐림_저녁' src='/images/cloudy_night.png' height='45px' title='흐림'/></a>";
                }
                break;
              default:
                sky = "알수 없는 값입니다.";
            }
            break;
          case "PTY": // 강수형태
            switch (weatherData.fcstValue){
              case none:
                pty = "없음";
                break;
              case rain: // 비
                if("06" < time_weather < "18"){
                  pty = "<a><img alt='비' src='/images/rain.png' height='45px' title='비'/></a>";
                }else {
                  pty = "<a><img alt='비' src='/images/rain_night.png' height='45px' title='비'/></a>";
                }
                break;
              case snow_rain: // 비/눈
                if("06" < time_weather < "18"){
                  pty = "<a><img alt='비/눈' src='/images/snow+rain.png' height='45px' title='비/눈'/></a>";
                }else {
                  pty = "<a><img alt='비/눈' src='/images/snow+rain_night.png' height='45px' title='비/눈'/></a>";
                }
                break;
              case snow: // 눈
                if("06" < time_weather < "18"){
                  pty = "<a><img alt='눈' src='/images/snow.png' height='45px' title='눈'/></a>";
                }else {
                  pty = "<a><img alt='눈' src='/images/snow_night.png' height='45px' title='눈'/></a>";
                }
                break;
              case shower: // shower
                if("06" < time_weather < "18"){
                  pty = "<a><img alt='소나기' src='/images/shower.png' height='45px' title='소나기'/></a>";
                }else {
                  pty = "<a><img alt='소나기' src='/images/shower.png' height='45px' title='소나기'/></a>";
                }
                break;
              case raindrop: // 빗방울
                if("06" < time_weather < "18"){
                  pty = "<a><img alt='빗방울' src='/images/raindrop.png' height='45px' title='빗방울'/></a>";
                }else {
                  pty = "<a><img alt='빗방울' src='/images/raindrop_night.png' height='45px' title='빗방울'/></a>";
                }
                break;
              case raindrop_blowingsnow: // 빗방울/눈날림
                if("06" < time_weather < "18"){
                  pty = "<a><img alt='빗방울/눈날림' src='/images/raindrop+blowingsnow.png' height='45px' title='빗방울/눈날림'/></a>";
                }else {
                  pty = "<a><img alt='빗방울/눈날림' src='/images/raindrop+blowingsnow_night.png' height='45px' title='빗방울/눈날림'/></a>";
                }
                break;
              case blowingsnow: // 눈날림
                if("06" < time_weather < "18"){
                  pty = "<a><img alt='눈날림' src='/images/blowingsnow.png' height='45px' title='눈날림'/></a>";
                }else {
                  pty = "<a><img alt='눈날림' src='/images/blowingsnow_night.png' height='45px' title='눈날림'/></a>";
                }
                break;
              default:
                pty = "알수 없는 값 입니다";
            }
            break;
          default:
            t1h = "알수 없는 값입니다.";
            sky = "알수 없는 값입니다.";
            pty = "알수 없는 값입니다.";
        }
        // console.log("index : " +index +":"+t1h);

        $('#weatherData_table').show();
        if ( t1h != '' ) {
          $("#hour"+index).html(t1h);
          $("#aaa"+index).html(time_weather);
          index++;

        }
        if ( sky != '' ) {
          $("#nnn"+index1).html(sky);
          index1++;
        }


      }
      

    }
  }
}

기온 + 날씨상태 + 강수형태 이렇게 3가지를 받아서 사용하였다
아이콘은 여기서 받았으니 참고하시길 바란다

jsp

<div id="address_dong" class="bolder center"></div>
	<div id="weather">
	<div id="weather_Data">
		<table id="weatherData_table">
			<tr class="center bolder">
				<td id="aaa0"></td>
                <td id="aaa1"></td>
                <td id="aaa2"></td>
                <td id="aaa3"></td>
                <td id="aaa4"></td>
                <td id="aaa5"></td>
            </tr>
			<tr class="center">
                <td id="hour0"></td>
                <td id="hour1"></td>
                <td id="hour2"></td>
                <td id="hour3"></td>
                <td id="hour4"></td>
                <td id="hour5"></td>
            </tr>
            <tr class="center">
                <td id="nnn0"></td>
                <td id="nnn1"></td>
                <td id="nnn2"></td>
                <td id="nnn3"></td>
                <td id="nnn4"></td>
                <td id="nnn5"></td>
            </tr>
    </table>
</div>

2. java

@Controller
public class WeatherApiController {
    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    @Autowired
    private GridService gridService;

    @GetMapping("/weather")
    public @ResponseBody String restApiGetWeather(/*String x, String y, HttpServletRequest httpServletRequest*/) throws Exception {
        /*
            @ API LIST ~
            getUltraSrtNcst 초단기실황조회
            getUltraSrtFcst 초단기예보조회
            getVilageFcst 동네예보조회
            getFcstVersion 예보버전조회
        */
        logger.info("restApiGetWeather: ");

        double x = 37.47384;
        double y = 126.88158;

        GridService.LatXLngY grid = gridService.convertGRID_GPS(0, x, y);

        String nx= String.format("%.0f",grid.x);
        String ny= String.format("%.0f",grid.y);

        String key = Key.weather;
        String date = gridService.dateSet();
        String time = gridService.timeSet();

        String url = "http://apis.data.go.kr/1360000/VilageFcstInfoService_2.0/getUltraSrtFcst"
                + "?serviceKey=" + key
                + "&dataType=JSON"             // JSON, XML
                + "&numOfRows=60"              // 페이지 ROWS
                + "&pageNo=1"                  // 페이지 번호
                + "&base_date=" + date         // 발표일자
                + "&base_time=" + time         // 발표시각
                + "&nx=" + nx                   // 예보지점 X 좌표
                + "&ny=" + ny ;                 // 예보지점 Y 좌표

        HashMap<String, Object> resultMap = getDataFromJson(url, "UTF-8", "get", "");

        logger.info("restApiGetWeather RESULT: "+ resultMap);

        JSONObject jsonObj = new JSONObject();
        jsonObj.put("result", resultMap);

        return jsonObj.toString();
    }

    public HashMap<String, Object> getDataFromJson(String url, String encoding, String type, String jsonStr) throws Exception {
        boolean isPost = false;

        if ("post".equals(type)) {
            isPost = true;
        } else {
            url = "".equals(jsonStr) ? url : url + "?request=" + jsonStr;
        }

        return getStringFromURL(url, encoding, isPost, jsonStr, "application/json");
    }

    public HashMap<String, Object> getStringFromURL(String url, String encoding, boolean isPost, String parameter, String contentType) throws Exception {
        URL apiURL = new URL(url);

        HttpURLConnection conn = null;
        BufferedReader br = null;
        BufferedWriter bw = null;

        HashMap<String, Object> resultMap = new HashMap<String, Object>();

        try {
            conn = (HttpURLConnection) apiURL.openConnection();
            conn.setConnectTimeout(5000);
            conn.setReadTimeout(5000);
            conn.setDoOutput(true);

            if (isPost) {
                conn.setRequestMethod("POST");
                conn.setRequestProperty("Content-Type", contentType);
                conn.setRequestProperty("Accept", "*/*");
            } else {
                conn.setRequestMethod("GET");
            }

            conn.connect();

            if (isPost) {
                bw = new BufferedWriter(new OutputStreamWriter(conn.getOutputStream(), "UTF-8"));
                bw.write(parameter);
                bw.flush();
                bw = null;
            }

            br = new BufferedReader(new InputStreamReader(conn.getInputStream(), encoding));

            String line = null;

            StringBuffer result = new StringBuffer();

            while ((line = br.readLine()) != null) result.append(line);

            ObjectMapper mapper = new ObjectMapper();

            resultMap = mapper.readValue(result.toString(), HashMap.class);
        } catch (Exception e) {
            e.printStackTrace();
            throw new Exception(url + " interface failed" + e.toString());
        } finally {
            if (conn != null) conn.disconnect();
            if (br != null) br.close();
            if (bw != null) bw.close();
        }

        return resultMap;
    }
}

신기한게 북한 기상예보도 나옵니다..!
하지만 vworld는 북한주소는 안나오기 때문에 ~ 예보만 뜹니다

그리고 사진을 올리면 상태 뜨는거도 기상청을 따라해보았다 ㅎㅎ..
초반엔 내 사이트이고 후반엔 기상청 사이트!

코드를 보기 쉽게 더 깔끔하게 작성하고 싶은데.. 아직은 이게 가장 깔끔하게 하는 방법이다.. 고수분들 더 깔끔하게 하는 법을 아시면 알려주세요 ㅠㅠ

profile
backend developers

0개의 댓글