앱을 기획할때 날씨 API를 넣고자 하였다.
그래서 기상청_단기예보 ((구)_동네예보) 조회서비스 를 이용하였다.
신청은 여기서 하면 된다.
https://www.data.go.kr/tcs/dss/selectApiDataDetailView.do?publicDataPk=15084084
여러가지를 제공하는데 그 중에서 초단기예보 조회를 사용하고자했다.
초단기예보 조회는 매시간 30분에 생성되고 10분마다 최신 정보로 업데이트(기온, 습도, 바람)가 되어 앱에서 날씨 정보를 요청하면 최근에 예측한 날씨 정보를 받아올 수있을것이라 생각했다.
초단기예보의 요청 파라미터는 다음과 같다.
serviceKey :
서비스키로 나의 인증키를 넘겨주는 부분이다.
numOfRows :
한페이지의 결과수로 한페이지당 받아올 날씨정보의 수를 얘기한다.
초단기예보같은 경우 60개가 올수 있다.
날씨의 정보는 12개 받아올 수 있고 매 발표시간마다 6시간씩 예보하기 때문에 60개를 받아온다.
base_date :
이건 예보정보를 관측한 날짜를 입력하는 부분이다.
base_time :
이건 예보 정보를 관측한 관측 시간대를 입력하는 부분이다. 매 시간 마다 관측을 하는데 basetime은 0030 이런식으로 입력해야 한다.
nx :
격자좌표 x를 입력받는 부분이다.
ny :
격자좌표 y를 입력받는 부분이다.
nx, ny같은 경우 위도, 경도에 대해 좌표를 변환해야 한다.
https://wpioneer.tistory.com/191
response는 다음과 같다.
여기서 눈여겨 봐야할것은 category와 fcstValue이다.
아래 사진은 category이고 category에 오는 값이 fcstValue가 된다.
Base_Date, Base_Time 설정하기
12시 30분, 1시 30분, ... 30분 기준으로 날씨 정보가 갱신이된다.
그러므로 30분을 기준으로 하여 30분보다 이전이면 그전 시간의 정보를 받아오면 된다.
예를 들어, 2시 27에 값을 받아오고자 하면 1시 30분 값을 받아오면 된다.
그런데 00시 30분인 경우에 문제가 생긴다.
00시 29분과 같은 경우는 그 전날 23시 30분 값을 받아와야 한다. 따라서, 이 부분 처리를 해야한다.
private String setBaseTime(){
LocalTime currentTime = LocalTime.now();
String baseTime = String.format("%02d", currentTime.getHour())+"30";
if(currentTime.getMinute()<30) {
baseTime = String.format("%02d", currentTime.getHour() - 1) + "30";
if(currentTime.getHour() == 0) {
baseTime = "2330";
}
}
return baseTime;
}
private String setBaseDate(){
LocalDateTime currentDateTime = LocalDateTime.now();
String baseDate = currentDateTime.format(DateTimeFormatter.ofPattern("yyyyMMdd"));
if(currentDateTime.getMinute()<30) {
if(currentDateTime.getHour() == 0) {
baseDate = LocalDateTime.now().minusDays(1).format(DateTimeFormatter.ofPattern("yyyyMMdd"));
}
}
return baseDate;
}
요청 URI 만들기
초단기예보조회는 http://apis.data.go.kr/1360000/VilageFcstInfoService_2.0/getUltraSrtFcst 로 요청해야 한다.
요청에 필요한 것은 위에서 설명했다. 따라서 이를 기반으로 uri를 만들어준다.
private URI makeRequestURI(String x, String y) throws UnsupportedEncodingException, URISyntaxException {
String apiUrl = "http://apis.data.go.kr/1360000/VilageFcstInfoService_2.0/getUltraSrtFcst";
String nx = x;
String ny = y;
String baseDate = setBaseDate();
String baseTime = setBaseTime();
String dataType = "JSON";
String numOfRows = "250";
StringBuilder urlBuilder = new StringBuilder(apiUrl);
urlBuilder.append("?" + URLEncoder.encode("ServiceKey","UTF-8") + "="+serviceKey);
urlBuilder.append("&" + URLEncoder.encode("nx","UTF-8") + "=" + URLEncoder.encode(nx, "UTF-8")); //경도
urlBuilder.append("&" + URLEncoder.encode("ny","UTF-8") + "=" + URLEncoder.encode(ny, "UTF-8")); //위도
urlBuilder.append("&" + URLEncoder.encode("base_date","UTF-8") + "=" + URLEncoder.encode(baseDate, "UTF-8")); /* 조회하고싶은 날짜*/
urlBuilder.append("&" + URLEncoder.encode("base_time","UTF-8") + "=" + URLEncoder.encode(baseTime, "UTF-8"));
urlBuilder.append("&" + URLEncoder.encode("dataType","UTF-8") + "=" + URLEncoder.encode(dataType, "UTF-8")); /* 타입 */
urlBuilder.append("&" + URLEncoder.encode("numOfRows","UTF-8") + "=" + URLEncoder.encode(numOfRows, "UTF-8")); /* 한 페이지 결과 수 */
return new URI(urlBuilder.toString());
}
날씨 정보 가져오기
RestTemplate을 이용하여 통신하면 된다.
WeatherApiResponseDTO를 이용하여 JsonObject를 이용하지 않고 받아온 값을 DTO에 mapping 시켜준다.
필요한 데이터만 처리해서 리턴해줘야 한다.
초단기예보조회API는 10개의 카테고리에 대해 6시간을 예보한다. 나는 가장 가까운 시간에 대한 예보만 얻고 싶기 때문에 0, 6, 12,... 에 대한 값을 가져오면 된다.
public List<WeatherDTO> getWeather(String x, String y) throws IOException, ParseException, URISyntaxException {
URI uri = makeRequestURI(x, y);
RestTemplate template = new RestTemplate();
HttpEntity<String> entity = new HttpEntity<>(this.getHeaders());
ResponseEntity<WeatherApiResponseDTO> responseDto = template.exchange(uri, HttpMethod.GET, entity, WeatherApiResponseDTO.class);
List<WeatherItemDTO> itemList = responseDto.getBody().getResponse().getBody().getItems().getItem();
List<WeatherDTO> weatherDTOList = new ArrayList<>();
for(int i=0;i<10;i++) {
String category = itemList.get(i*6).getCategory();
String value = itemList.get(i*6).getFcstValue();
WeatherDTO weatherDTO = new WeatherDTO(category, value);
weatherDTOList.add(weatherDTO);
}
return weatherDTOList;
}
Controller 코드
@Tag(name = "Weather", description = "메인화면 날씨 API")
@RestController
@RequestMapping("/api/weather")
@RequiredArgsConstructor
public class WeatherController {
private final WeatherService weatherService;
private final ResponseService responseService;
@Operation(summary = "get weather", description = "지역에 대한 날씨를 가져오기")
@Parameters({
@Parameter(name = "x", description = "x좌표", example = "60"),
@Parameter(name = "y", description = "y좌표", example = "127"),
})
@GetMapping("")
public ListResponse<WeatherDTO> getWeather(@RequestParam("x") String x, @RequestParam("y") String y) throws IOException, ParseException, URISyntaxException {
return responseService.getListResponse(weatherService.getWeather(x, y));
}
}
Service 전체 코드
@Service
public class WeatherService {
@Value("${weather.api.key}")
private String serviceKey;
public List<WeatherDTO> getWeather(String x, String y) throws IOException, ParseException, URISyntaxException {
URI uri = makeRequestURI(x, y);
RestTemplate template = new RestTemplate();
HttpEntity<String> entity = new HttpEntity<>(this.getHeaders());
ResponseEntity<WeatherApiResponseDTO> responseDto = template.exchange(uri, HttpMethod.GET, entity, WeatherApiResponseDTO.class);
List<WeatherItemDTO> itemList = responseDto.getBody().getResponse().getBody().getItems().getItem();
List<WeatherDTO> weatherDTOList = new ArrayList<>();
for(int i=0;i<10;i++) {
String category = itemList.get(i*6).getCategory();
String value = itemList.get(i*6).getFcstValue();
WeatherDTO weatherDTO = new WeatherDTO(category, value);
weatherDTOList.add(weatherDTO);
}
return weatherDTOList;
}
private URI makeRequestURI(String x, String y) throws UnsupportedEncodingException, URISyntaxException {
String apiUrl = "http://apis.data.go.kr/1360000/VilageFcstInfoService_2.0/getUltraSrtFcst";
String nx = x;
String ny = y;
String baseDate = setBaseDate();
String baseTime = setBaseTime();
String dataType = "JSON";
String numOfRows = "250";
StringBuilder urlBuilder = new StringBuilder(apiUrl);
urlBuilder.append("?" + URLEncoder.encode("ServiceKey","UTF-8") + "="+serviceKey);
urlBuilder.append("&" + URLEncoder.encode("nx","UTF-8") + "=" + URLEncoder.encode(nx, "UTF-8")); //경도
urlBuilder.append("&" + URLEncoder.encode("ny","UTF-8") + "=" + URLEncoder.encode(ny, "UTF-8")); //위도
urlBuilder.append("&" + URLEncoder.encode("base_date","UTF-8") + "=" + URLEncoder.encode(baseDate, "UTF-8")); /* 조회하고싶은 날짜*/
urlBuilder.append("&" + URLEncoder.encode("base_time","UTF-8") + "=" + URLEncoder.encode(baseTime, "UTF-8"));
urlBuilder.append("&" + URLEncoder.encode("dataType","UTF-8") + "=" + URLEncoder.encode(dataType, "UTF-8")); /* 타입 */
urlBuilder.append("&" + URLEncoder.encode("numOfRows","UTF-8") + "=" + URLEncoder.encode(numOfRows, "UTF-8")); /* 한 페이지 결과 수 */
return new URI(urlBuilder.toString());
}
private String setBaseDate(){
LocalDateTime currentDateTime = LocalDateTime.now();
String baseDate = currentDateTime.format(DateTimeFormatter.ofPattern("yyyyMMdd"));
if(currentDateTime.getMinute()<30) {
if(currentDateTime.getHour() == 0) {
baseDate = LocalDateTime.now().minusDays(1).format(DateTimeFormatter.ofPattern("yyyyMMdd"));
}
}
return baseDate;
}
private String setBaseTime(){
LocalTime currentTime = LocalTime.now();
String baseTime = String.format("%02d", currentTime.getHour())+"30";
if(currentTime.getMinute()<30) {
baseTime = String.format("%02d", currentTime.getHour() - 1) + "30";
if(currentTime.getHour() == 0) {
baseTime = "2330";
}
}
return baseTime;
}
private HttpHeaders getHeaders() {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(new MediaType("application", "JSON", Charset.forName("UTF-8")));
return headers;
}
}
DTO
import lombok.Data;
@Data
public class WeatherApiResponseDTO
{
private WeatherResponseDTO response;
}
WeatherResponseDTO - 위의 WeatherApiResponseDTO에서 response객체에서 header, body를 분리하기 위해 만드는 DTO
import lombok.Data;
@Data
public class WeatherResponseDTO
{
private WeatherHeaderDTO header;
private WeatherBodyDTO body;
}
WeatherHeaderDTO - 위의 responseDTO에서 header와 매칭되는 DTO
import lombok.Data;
@Data
public class WeatherHeaderDTO
{
private String resultCode;
private String resultMsg;
}
WeatherBodyDTO - 위의 responseDTO에서 body와 매칭되는 DTO
import lombok.Data;
@Data
public class WeatherBodyDTO
{
private String dataType;
private WeatherItemsDTO items;
}
WeatherItemsDTO - BodyDTO에서 items의 내용을 가져오기 위한 DTO
import java.util.List;
import lombok.Data;
@Data
public class WeatherItemsDTO
{
private List<WeatherItemDTO> item;
private int numOfRows;
private int pageNo;
private int totalCount;
}
WeatherItemDTO - ItemsDTO에서 각각의 실제 응답값을 가져오기 위한 DTO
import lombok.Data;
@Data
public class WeatherItemDTO
{
private String baseDate;
private String baseTime;
private String category;
private String nx;
private String ny;
private String fcstValue;
}
WeatherDTO - View단으로 보내기 위한 DTO
@Data
public class WeatherDTO
{
// 자료구분코드
private String category;
// 실황 값
private String fcstValue;
}
참고문서
- https://wpioneer.tistory.com/192
- https://ming9mon.tistory.com/151
- https://velog.io/@with667800/%EC%98%A4%ED%94%88-API-%EA%B8%B0%EC%83%81%EC%B2%AD-%EB%8B%A8%EA%B8%B0%EC%98%88%EB%B3%B42