이전에 위도/경도로 주소를 불러오는 것을 해보았는데
이번에는 위도/경도로 기상청 api를 불러와서 연결해보려고 한다
우선 준비물은 api를 가져오는 것!
기상청단기예보 ((구)동네예보) 조회서비스 <-- 요기서 확인가능!
로그인하고 활용신청을 하고 apikey를 받은 뒤에 사용해주면된다
참고자료는 필요한 코드를 볼수 있으니 꼭 받아서 보길바란다!
초단기예보 << 나는 이것을 이용하였다
홈페이지 참고문서쪽에 보면 위도/경도를 가져와서 띄우는게아니라
gridx,gridy --> x,y 좌표를 통해 가져오기 때문에 변환을 해준후에 가져와야한다
문서에는 c언어만 변환 방법을 알려줬는데 여기가면 친절한 분들이 올려두셧다
언어별로 종류가 엄청 많으니 꼭 java, javascript가 아닌분들은 참고해도 좋을 것 같다
기상청 x,y 변환방법 <-- 요기서 확인가능!
기상청 사이트에서도 확인을 해볼 수 있는데
기상청 날씨누리 들어가게되면 페이지 소스 보기를 한 후에
app-weather-forecast-short-term.js <-- 요기서 확인가능!
여기 맨 아래를 보면 javascript를 이용하여 변환한 방법이 올라와있다
이것도 2가지 방법으로 해보았는데
javascript는 전체적으로 다올렸고
java로 하는 부분은 그냥 데이터만 들어왔는지 확인하는거까지 진행하였다
/*
* 위도경도 좌표 변환
*/
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;
}
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가지를 받아서 사용하였다
아이콘은 여기서 받았으니 참고하시길 바란다
<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>
@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는 북한주소는 안나오기 때문에 ~ 예보만 뜹니다
그리고 사진을 올리면 상태 뜨는거도 기상청을 따라해보았다 ㅎㅎ..
초반엔 내 사이트이고 후반엔 기상청 사이트!
코드를 보기 쉽게 더 깔끔하게 작성하고 싶은데.. 아직은 이게 가장 깔끔하게 하는 방법이다.. 고수분들 더 깔끔하게 하는 법을 아시면 알려주세요 ㅠㅠ