Project/AWS-Final

최종 프로젝트(5) - AWS 풀스택 과정 94일차

awspspgh 2024. 12. 11. 10:01
목차
1. 지도 & 날씨 연동
2. properties

 

1. 지도 & 날씨 연동

▶ 코드

◈ index.html

<!DOCTYPE html>
<html lang="en"
      xmlns:th="http://www.thymeleaf.org"
      xmlns:layout="http://www.ultrap.net.nz/thymeleaf/layout"
      layout:decorate="~{layout/layout}">

<body>
<div layout:fragment="content">
  <div class="container-md">
    <!--    카카오맵 API 로딩 (HTTPS 사용)-->
    <script type="text/javascript"
            th:src="@{'https://dapi.kakao.com/v2/maps/sdk.js?appkey=내 API Key 입력&libraries=services'}"></script>

<!--      <button onclick="getLocation()">주변 인기 장소 찾기</button>-->
      <p id="status">위치를 찾지 못했습니다</p>
  <!--    <h1>Geolocation API로 현재 위치 가져오기</h1>-->
      <div id="map" style="width:500px;height:400px;"></div>
      <ul id="places"></ul>

      <script th:src="@{/js/currentLocationMap.js}"></script>

      <hr>

<!--    <form th:action="@{/}" method="post">-->
<!--      <input name="address" placeholder="도로명 주소를 입력해주세요."/>-->

<!--      <button type="submit">입력</button>-->
<!--    </form>-->

    <form id="addressForm" th:action="@{/}" method="post" style="display: none;">
      <input id="addressInput" name="address" type="text" />
    </form>

    <div th:unless="${weather == null}">
      <li th:text="'날씨 : ' + ${weather['weather_main']}"></li>
      <li th:text="'날씨 설명 : ' + ${weather['weather_description']}"></li>
      <li th:text="'온도 : ' + ${weather['temperature']}"></li>
      <li th:text="'습도 : ' + ${weather['humidity']}"></li>
    </div>

    <hr>

    <script>

    </script>

  </div>
</div>

</body>
</html>

 

◈ currentLocationMap.js

    let map;
    let geocoder;

    function initializeMap() {
      // Geocoder 객체 생성
      geocoder = new kakao.maps.services.Geocoder();
  
      // 페이지 로드 시 위치 정보 가져오기
      getLocation();
  }

    function getLocation() {
      const status = document.getElementById("status");

      // Geolocation API 지원 여부 확인
      if ("geolocation" in navigator) {
        navigator.geolocation.getCurrentPosition(
          (position) => {
            const { latitude, longitude } = position.coords;
            status.textContent = `위도: ${latitude}, 경도: ${longitude}`;

            // Kakao Maps Geocoder로 도로명 주소 가져오기
            const coords = new kakao.maps.LatLng(latitude, longitude);

            // 지도를 해당 위치로 이동
            const mapContainer = document.getElementById('map');
            const mapOption = {
              center: coords, // 지도의 중심 좌표 설정
              level: 7  // 지도 확대 수준(레벨) 설정
            };
            map = new kakao.maps.Map(mapContainer, mapOption);

            // 마커 표시
            const marker = new kakao.maps.Marker({
              position: coords,
              map: map
            });
            // marker.setMap(map);

            // 주소 변환
            geocoder.coord2Address(coords.getLng(), coords.getLat(), (result, status) => {
              console.log("API 호출 결과: ", result);
              console.log("API 상태: ", status);

              if (status === kakao.maps.services.Status.OK) {
                  // console.log("result.length: ", result.length);
                  // console.log("result[0].road_address: ", result[0].road_address);

                  if (result.length > 0) {
                    // 도로명 주소가 있으면 도로명 주소 사용, 없으면 일반 주소 사용
                    const region_1depth_name = result[0]?.address?.region_1depth_name;
                    const region_2depth_name = result[0]?.address?.region_2depth_name;
                    const region_3depth_name = result[0]?.address?.region_3depth_name;
                    const depthAddress = region_1depth_name + region_2depth_name + region_3depth_name;
                    
                    // console.log("roadAddress: ", roadAddress);
                    // console.log("result[0]?.address_name: ", result[0]?.address_name);
                    // console.log("주소: ", address);

                      if (depthAddress) {
                          console.log("도로명 주소: ", depthAddress);

                          // 페이지 로드 후 한 번만 자동 제출하도록 설정
                          const addressInput = document.getElementById("addressInput");
                          addressInput.value = depthAddress;

                          // 'addressForm' 제출 방지 여부 확인
                          if (!sessionStorage.getItem('addressSubmitted')) {
                            const addressForm = document.getElementById("addressForm");
                            addressForm.submit();

                            // 제출한 후 sessionStorage에 표시
                            sessionStorage.setItem('addressSubmitted', 'true');
                          }
                      } else {
                          console.error("도로명 주소를 찾을 수 없습니다.");
                      }
                  } else {
                      console.error("주소 변환에 실패했습니다.");
                  }
              } else {
                  console.log("coord2Address API 호출 실패: ", status);
              }
            });

          },
          (error) => {
            status.textContent = `위치 정보를 가져올 수 없습니다: ${error.message}`;
          },
          {
            enableHighAccuracy: true, // 정확도 우선 모드
            timeout: 10000,           // 10초 이내에 응답 없으면 에러 발생
            maximumAge: 0             // 항상 최신 위치 정보 수집
          }
        );
      } else {
        status.textContent = "브라우저가 위치 서비스를 지원하지 않습니다.";
      }
    }

    // Kakao 지도 API가 로드된 후 initializeMap 호출
    window.onload = initializeMap;

 // kakao map API로 지도 띄우기
/* var container = document.getElementById('map');
       var options = {
           center: new kakao.maps.LatLng(33.450701, 126.570667),
           level: 3
       };

       var map = new kakao.maps.Map(container, options);*/

 

▷ 출력

 

2. properties

◈ WeatherService.java

package com.project.joonggo.service;

import org.springframework.beans.factory.annotation.Value;
import org.json.JSONObject;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Service;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;


@Service
@PropertySource("classpath:config.properties")
public class WeatherService {
    @Value("${Geocoder_API_KEY}")
    private String Geocoder_API_KEY;

    @Value("${OpenWeatherMap_API_KEY}")
    private String OpenWeatherMap_API_KEY;

    // 위도와 경도를 Map<String, String> 형태로 반환하는 메서드
    public Map<String, String> returnLanLon(String address){
        Map<String, String> coordinates = new HashMap<>();
        String apiKey = Geocoder_API_KEY; // Geocoder API 2.0에서 발급받은 API Key 작성할 것

        try{
            // 주소 인코딩 (인코딩 X 시 400 에러 발생함)
            String encodedAddress = URLEncoder.encode(address, StandardCharsets.UTF_8.toString());
            String apiUrl = "https://api.vworld.kr/req/address?service=address&request=getCoord&key=" + apiKey + "&type=ROAD&address=" + encodedAddress;

            // URL 객체 생성
            URL url = new URL(apiUrl);

            // 연결 설정
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            conn.setRequestMethod("GET");

            // 응답 코드 확인
            if(conn.getResponseCode() == HttpURLConnection.HTTP_OK){
                BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
                StringBuilder response = new StringBuilder();
                String inputLine;

                // 응답 내용 읽기
                while ((inputLine = in.readLine()) != null){
                    response.append(inputLine);
                }
                in.close();

                // JSON 응답 파싱
                JSONObject jsonResponse = new JSONObject(response.toString());
                if(jsonResponse.getJSONObject("response").getString("status").equals("OK")) {
                    String x = jsonResponse.getJSONObject("response").getJSONObject("result").getJSONObject("point").getString("x");
                    String y = jsonResponse.getJSONObject("response").getJSONObject("result").getJSONObject("point").getString("y");
                    coordinates.put("lat", y);
                    coordinates.put("lon", x);
                }
            }else{
                System.out.println("Error: " + conn.getResponseCode());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        return coordinates;
    }

    // 날씨 정보를 Map<String, String> 형태로 반환하는 메서드
    public Map<String, String> returnWeather(Map<String, String> lanLon){
        String apiKey = OpenWeatherMap_API_KEY; // Open Weather Map 날씨 API에서 발급받은 API Key 작성할 것

        // 넘어온 위도, 경도를 포함한 url
        String apiUrl = "https://api.openweathermap.org/data/2.5/weather?lat=" + lanLon.get("lat") + "&lon=" + lanLon.get("lon") + "&appid=" + apiKey;

        Map<String, String> weather = new HashMap<>();

        try {
            // URL 객체 생성
            URL url = new URL(apiUrl);
            // 연결 설정
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            conn.setRequestMethod("GET");

            // 응답 코드 확인
            if(conn.getResponseCode() == HttpURLConnection.HTTP_OK){
                BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
                StringBuilder response = new StringBuilder();
                String inputLine;

                // 응답 내용 읽기
                while((inputLine = in.readLine()) != null){
                    response.append(inputLine);
                }
                in.close();

                // JSON 응답 파싱
                JSONObject jsonResponse = new JSONObject(response.toString());

                weather.put("weather_main", jsonResponse.getJSONArray("weather").getJSONObject(0).getString("main"));
                weather.put("weather_description", jsonResponse.getJSONArray("weather").getJSONObject(0).getString("description"));

                BigDecimal temp = jsonResponse.getJSONObject("main").getBigDecimal("temp");
                BigDecimal tempCelsius = BigDecimal.valueOf(temp.doubleValue() - 273.15).setScale(2, RoundingMode.HALF_UP); // 소수점 둘째 자리에서 반올림

                weather.put("temperature", String.valueOf(tempCelsius));

                int humidity = jsonResponse.getJSONObject("main").getInt("humidity");
                weather.put("humidity", Integer.toString(humidity) + "%");
            } else {
                System.out.println("Error: " + conn.getResponseCode());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        return weather;
    }

}

 

◈ WeatherController.java

package com.project.joonggo.controller;

import com.project.joonggo.service.WeatherService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;

import java.util.Map;

@Controller
@RequiredArgsConstructor
public class WeatherController {

    private final WeatherService weatherService;

//    @Value("${KakaoMap_API_KEY}")
//    private String KakaoMap_API_KEY;

    @GetMapping
    public String home(){
        return "index";
    }

//    @GetMapping
//    public String home(Model model) {
//        model.addAttribute("KakaoMap_API_KEY", KakaoMap_API_KEY);
//        return "index";
//    }

    @PostMapping("/")
    public String showWeather(@RequestParam("address") String address, Model model){
        Map<String, String> lanLon = weatherService.returnLanLon(address);

        model.addAttribute("weather", weatherService.returnWeather(lanLon));
        return "index";
    }
}

 

1. config.properties 파일 생성

src/main/resources 하위에 config.properties 파일 생성 후, 다음과 같이 API KEY 값 작성

src/main/resources 하위에 config.properties 파일 생성 후, 다음과 같이 API KEY 값 작성

 

2. @ProvertySoursece("classpath:config.properties")

API KEY를 사용하고자 하는 파일에 @PropertySource 어노테이션 작성

@PropertySource("classpath:config.properties")
public class SampleController {
	...
}

 

3. API KEY 불러오기

@Value 어노테이션을 통해 config.properties 파일에서 설정해둔 변수명으로 값 주입

@Value("${SAMPLE_API_KEY}")
private String API_KEY;

 

4. .gitignore에 추가

보안을 위해 .gitignore 파일에 config.properties 추가

config.properties

 

▣ 참고 자료 출처

- 좌표를 도로명 주소로 변환

https://apis.map.kakao.com/web/sample/coord2addr/

 

- properties

https://velog.io/@cykim_/SpringBoot-properties-%ED%8C%8C%EC%9D%BC%EC%97%90-API-KEY-%EC%A0%80%EC%9E%A5%ED%95%98%EA%B8%B0

 

[SpringBoot] properties 파일에 API KEY 저장하기

src/main/resources 하위에 config.properties 파일 생성 후, 다음과 같이 API KEY 값 작성API KEY를 사용하고자 하는 파일에 @PropertySource 어노테이션 작성@Value 어노테이션을 통해 config.properties

velog.io