728x90

개요

저번 포스트에서 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시간 후 날씨도 같이 보여지는 것이다. 보여지는게 싫다면 자바 코드에서 수정하면 되지만.. 굳이!? 

화면단 구성 완료..... 휴 힘들었다 ㅜ.ㅜ

 

728x90

+ Recent posts