개요
저번 포스트에서 2주 체험판 weather api를 사용했는데 2주가 끝나면 3일간의 날씨가 보여줄 수가 있다고 했다.
그래서 현재 코드는 7일간의 날씨가 보이도록 했지만 무료버전으로 바뀌게 되면 오류가 발생할 것 같아서 다른 방안을 찾아 다녔다.
그러다 제일 흔한 OpenWeather API의 무료버전에서 5일간의 날씨를 보여줄 수 있다고 한다.!!! 비록 일주일은 아니지만 5일만의 날씨가 보여지는 것만으로도 만족한다..ㅎㅎㅎ
프로젝트 구성
Current weather and forecast - OpenWeatherMap
Current weather and forecast - OpenWeatherMap
OpenWeather Weather forecasts, nowcasts and history in a fast and elegant way
openweathermap.org
여기서 회원가입 후 My API Keys를 들어가면 기본적인 API 키가 있을 것이다. 그게 끝이다...ㅎㅎ
그리고 가입한 이메일을 가서 인증을 하고 1시간정도 기다리면 이제 api 키가 활성화가 되어서 사용이 가능해진다.
- 이때 발급하면 나오는 API KEY를 application.yml 파일에 저장한다.
- 알려져서는 안되는 키이다.
weather:
api:
key: {API key}
- 그 후에 스프링에서 날씨 api를 불러쓰기 위해서 RestTemplate를 빈으로 등록한다.
@Configuration
public class DemoApplication {
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
- RestTemplate 이란?
- HTTP 요청을 보내고 응답을 받기 위해 사용하는 클라이언트이다.
- 외부 RESTful 웹 서비스와 통신할 때 사용된다.
- 다른 서버의 API를 호출하여 데이터를 가져오거나, 서버로 데이터를 전송할 때 사용된다.
- 왜 @Bean으로 등록?
- Spring 컨텍스트 내에서 공유되며, 다른 컴포넌트에서 @Autowired를 통해 주입받아 사용할 수 있다.
날씨 API call 살펴보기
- 그 다음으로 사이트에 들어가서 API -> {원하는 date} -> API doc 를 들어가서 url을 어떤식으로 작성해야 하는지 확인한다.
- 하지만 무료버전/ 유료버전에 따라 다르기 때문에 잘 보고 해야한다.
- 나는 현재날씨와 5일간의 날씨 2가지를 다 가져와서 보여지게 할 것이다.
- 만약 현재 날씨만 가져올 것이면 Current Weather Data에 들어가서 보면 어떤 식으로 불러올지 나와있다.
- 나는 현재 날씨는 지오코딩 api를 사용했다.
지오코딩은 번역기 돌려서..
이렇게 얘기한다 ㅎㅅㅎ지오코딩을 사용해서 현재 위치를 보여준 이유는 공식 문서에서 지오코딩 어쩌고 더 간단하다고 써봤는데...
만약 지오코딩을 써서 현재의 날씨를 받아오면 아래와 같은 json으로 출력된다.
[
{
"name":"London",
"local_names":{
},
"lat":51.5073219,
"lon":-0.1276474,
"country":"GB",
"state":"England"
},
- 그리고 5일간의 날씨 데이터와 3시간 간격으로 보여지도록 할 것이기에 5 Day / 3 Hour Forecast -> API doc 에 들어간다.
- 공식 문서에서 알려주는 듯이 api call 형태로 위도와 경도를 받아와서 보여주는 식으로 진행할 것이다.
- 그 다음으로 yml 저장한 키를 토대로 api 를 불러오기 위한 컨트롤러를 작성한다.
@RestController
@RequiredArgsConstructor
public class WeatherApiController {
@Value("${weather.api.key}")
private String apiKey;
private final RestTemplate restTemplate;
private final ObjectMapper objectMapper;
@GetMapping("/api/weather")
public ResponseEntity<String> getWeather(@RequestParam String location) {
String geocodeUrl = String.format("http://api.openweathermap.org/geo/1.0/direct?q=%s&limit=1&appid=%s",
location, apiKey);
try {
ResponseEntity<String> geocodeResponse = restTemplate.getForEntity(geocodeUrl, String.class);
if (geocodeResponse.getStatusCode() != HttpStatus.OK || geocodeResponse.getBody() == null) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("위치 정보를 찾을 수 없습니다.");
}
JsonNode geocodeJsonArray = objectMapper.readTree(geocodeResponse.getBody());
if (geocodeJsonArray.isEmpty()) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("위치 정보를 찾을 수 없습니다.");
}
JsonNode locationJson = geocodeJsonArray.get(0);
double lat = locationJson.get("lat").asDouble();
double lon = locationJson.get("lon").asDouble();
// 5일 날씨 예보 URL
String forecastUrl = String.format("https://api.openweathermap.org/data/2.5/forecast?lat=%s&lon=%s&appid=%s&units=metric",
lat, lon, apiKey);
String forecastResponse = restTemplate.getForObject(forecastUrl, String.class);
return ResponseEntity.ok(forecastResponse);
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("날씨 정보를 가져오는 데 실패했습니다.");
}
}
}
- 위 코드는 지오코딩으로 현재 날씨에서 json을 받아오고 응답한 뒤, 그 값에서 아래의 코드를 통해 경도와 위도를 받아온다.
JsonNode locationJson = geocodeJsonArray.get(0);
double lat = locationJson.get("lat").asDouble();
double lon = locationJson.get("lon").asDouble();
- 위에서 봤듯이 json 출력 배열의 첫 번째 요소 중 경도와 위도를 가져오는 부분이다.
- 특정 위치의 날씨 정보를 가져오는 데 필요하다.
HTML, JS 코드
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>OMG Travel</title>
<script src='https://cdn.jsdelivr.net/npm/fullcalendar@6.1.15/index.global.min.js'></script>
<link rel="stylesheet" href="/css/header.css">
</head>
<body>
<div th:replace="fragments/header :: headerFragment"></div>
<div class="wrapper">
<div class="container">
<div class="left-content">
<div class="weather-section">
<div class="search-container">
<input type="text" id="location-input" class="search-input" placeholder="지역 입력">
<button id="search-button" class="search-button">검색</button>
</div>
<div>
<h3>현재 날씨</h3>
<div id="current-weather"></div>
<div id="current-date" style="display: none;"></div>
<h3>이번 주 날씨</h3>
<div class="weather" id="weather-container"></div>
</div>
</div>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
<!-- 날씨 API 불러오기 -->
const weatherContainer = document.getElementById('weather-container');
const currentWeather = document.getElementById('current-weather');
const currentDateEl = document.getElementById('current-date');
function fetchWeather(location) {
fetch(`/api/weather?location=${encodeURIComponent(location)}`)
.then(response => {
if (!response.ok) {
throw new Error(`HTTP 접속 오류 상태: ${response.status}`);
}
return response.json();
})
.then(data => {
// 날짜별로 날씨 정보를 집계
const weatherMap = {};
data.list.forEach(item => {
const date = new Date(item.dt * 1000);
const options = {weekday: 'short', month: 'short', day: 'numeric'};
const formattedDate = date.toLocaleDateString('ko-KR', options);
if (!weatherMap[formattedDate]) {
weatherMap[formattedDate] = {
tempSum: 0,
count: 0,
weatherDescription: item.weather[0].description,
iconCode: item.weather[0].icon
};
}
weatherMap[formattedDate].tempSum += item.main.temp;
weatherMap[formattedDate].count += 1;
});
// 평균 온도 계산 및 출력
const weatherItems = Object.keys(weatherMap).map(date => {
const weatherInfo = weatherMap[date];
const averageTemp = (weatherInfo.tempSum / weatherInfo.count).toFixed(1);
const iconUrl = `http://openweathermap.org/img/wn/${weatherInfo.iconCode}@2x.png`;
return `
<div class="weather-item">
<div class="day">${date}</div>
<div class="temp">${averageTemp}°C</div>
<img src="${iconUrl}" alt="날씨 아이콘" class="weather-icon"/>
<div>${weatherInfo.weatherDescription}</div>
</div>
`;
}).join('');
weatherContainer.innerHTML = weatherItems;
const today = new Date();
const options = {year: 'numeric', month: 'long', day: 'numeric'};
const formattedDate = today.toLocaleDateString('ko-KR', options);
// 현재 날씨를 보여주는 부분
const currentItem = data.list[0];
currentWeather.innerHTML = `
<div class="current-temp">${currentItem.main.temp}°C</div>
<div class="current-description">${currentItem.weather[0].description}</div>
<img src="http://openweathermap.org/img/wn/${currentItem.weather[0].icon}@2x.png" alt="날씨 아이콘" class="weather-icon"/>
`;
currentDateEl.innerHTML = `<div>${formattedDate}</div>`;
})
.catch(error => console.error('날씨 정보를 가져오지 못했습니다:', error));
}
fetchWeather('Seoul'); // 기본값 서울
document.getElementById('search-button').addEventListener('click', function () {
const locationInput = document.getElementById('location-input').value;
if (locationInput) {
fetchWeather(locationInput);
} else {
alert('지역 이름을 입력하세요.');
}
});
document.getElementById('location-input').addEventListener('keypress', function (e) {
if (e.key === 'Enter') {
document.getElementById('search-button').click();
}
});
});
</script>
</body>
</html>
위와 같이 코드를 작성하면 화면단에 날씨가 보인다!!
참고로 왜 5일간의 데이터가 보인다고 했는데 실제로 보이는건 오늘 날짜를 포함한 6일의 날씨가 보인다. 이건
open weather API에서 제공하는 자체가 5일간의 데이터 + 3시간 후 날씨 여서 현재 날씨의 3시간 후 날씨도 같이 보여지는 것이다. 보여지는게 싫다면 자바 코드에서 수정하면 되지만.. 굳이!?
'Spring' 카테고리의 다른 글
날씨 API 불러오기 - Weather API 사용 (0) | 2024.08.22 |
---|---|
Spring : @Controller와 @RestController의 차이 (0) | 2024.08.02 |
Spring JPA :JpaRepository 상속 시 메서드 이름 구성 (0) | 2024.07.25 |
AWS : AWS IAM, AWS CLI (0) | 2024.06.21 |
[스프링 핵심 원리 - 기본편] #4 인프런 강의 정리(객체 지향 원리 적용 - 새로운 할인 정책 개발, 적용, 문제점) (2) | 2024.06.11 |