Project/AWS-Final

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

awspspgh 2024. 12. 10. 09:40
목차
1. 현재 위치
2. 날씨

 

1. 현재 위치

Geolocation API

: Geolocation API는 웹 브라우저에서 사용자의 위치 정보를 가져올 수 있는 표준 API이다.

GPS, Wi-Fi, 셀룰러 네트워크, IP 주소 등의 데이터를 활용하여 사용자의 현재 위치를 확인한다.

 

특징

- 사용자 동의 필수: 브라우저는 사용자의 위치 정보를 수집하기 전 동의를 요구한다.

- 다양한 위치 수집 소스: GPS, Wi-Fi, 셀룰러 네트워크, IP 주소 등 여러 소스로부터 정보를 수집한다.

- 높은 정확도 지원: enableHighAccuracy 옵션을 통해 정확도를 조정할 수 있다.

- 브라우저 지원: 대부분의 현대 브라우저에서 지원되며, 모바일에서도 동작한다.

 

Geolocation API 사용 방법

: 주로 두 가지 주요 메서드를 통해 사용된다.

1. getCurrentPosition: 한 번만 위치를 가져온다.

2. watchPosition: 위치가 변할 때마다 지속적으로 위치 정보를 제공한다.

 

Geolocation API 옵션 설명

navigator.geolocation.getCurrentPosition 및

navigator.geolocation.watchPosition 메서드에는 선택할 수 있는 옵션들이 있다.

- enableHighAccuracy: 고정밀 모드를 활성화한다. 배터리 소모가 높아질 수 있다.

- timeout: 위치 정보를 가져올 때까지 기다리는 최대 시간을 밀리초 단위로 설정한다.

- maximumAge: 캐시된 위치 정보가 제공될 수 있는 최대 시간을 밀리초 단위로 설정한다.

 

카카오맵 API 연동 및 키워드 검색

1. 카카오 개발자 페이지 가입 및 앱 키 발급

- 카카오 개발자 페이지에 회원가입하고, 앱을 생성하여 JavaScript 키를 발급받는다.

 

2. 카카오맵 API 스크립트 추가

- index.html 파일에 다음 스크립트를 추가한다.

<script type="text/javascript" src="//dapi.kakao.com/v2/maps/sdk.js?appkey=YOUR_APP_KEY&libraries=services"></script>

- YOUR_APP_KEY 부분에 발급받은 JavaScript 키를 입력한다.

 

코드

 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 키 입력}"></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>
  </div>
</div>

</body>
</html>

 

currentLocationMap.js

    let map;
    let ps;

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

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

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

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

            // // 장소 검색 객체 생성
            // ps = new kakao.maps.services.Places();

            // // 주변 나들이 장소 검색
            // const placesList = document.getElementById("places");
            // ps.keywordSearch("관광지", (data, status, pagination) => {
            //   if (status === kakao.maps.services.Status.OK) {
            //     placesList.innerHTML = "";
            //     data.forEach((place) => {
            //       const listItem = document.createElement("li");
            //       listItem.textContent = `${place.place_name} (${place.address_name})`;
            //       placesList.appendChild(listItem);

            //       // 지도에 마커 추가
            //       const placeMarker = new kakao.maps.Marker({
            //         position: new kakao.maps.LatLng(place.y, place.x),
            //         map: map
            //       });
            //     });
            //   } else {
            //     placesList.textContent = "주변 관광지를 찾을 수 없습니다.";
            //   }
            // }, {
            //   location: new kakao.maps.LatLng(latitude, longitude),
            //   radius: 5000 // 5km 내에서 검색
            // });
          },
          (error) => {
            status.textContent = `위치 정보를 가져올 수 없습니다: ${error.message}`;
          },
          {
            enableHighAccuracy: true, // 정확도 우선 모드
            timeout: 10000,           // 10초 이내에 응답 없으면 에러 발생
            maximumAge: 0             // 항상 최신 위치 정보 수집
          }
        );
      } else {
        status.textContent = "브라우저가 위치 서비스를 지원하지 않습니다.";
      }
    }

 // 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. 날씨

Geocoder API 2.0

- open weather map의 현재 날씨 정보 API를 사용하기 위해서는 요청에 위도, 경도를 포함해야 한다.

- 내 주소 (혹은 날씨를 알고 싶어하는 주소)의 위도, 경도를 알기 위해 사용한다.

 

Geocoder API 사용 방법

- 해당 사이트에 접속 후 회원가입을 한다.

https://www.vworld.kr/v4po_main.do

 

브이월드

국가가 보유하고 있는 공개 가능한 공간정보를 모든 국민이 자유롭게 활용할 수 있도록 다양한 방법을 제공합니다.

www.vworld.kr

 

 

- 상단의 메뉴바를 클릭

 

- 오픈 API > 인증키발급을 클릭

 

- 약관 동의 후 이용 정보 작성 (아래는 예시)

  서비스명: 날씨 웹 서비스 예제 프로젝트

  서비스 분류: 교육

  서비스 유형: 웹사이트

  서비스 URL : localhost:8089

  서비스 설명

    개요: 날씨 웹 서비스 예제 프로젝트

     서비스 대상: 로컬(개인)

    목적: open weather map API와 연동하여 주소 입력을 통한 날씨 정보를 가져오는 프로젝트

  활용 API : 지오코더 API (반드시 선택!)

  사용기관: 민간, 개인

 

- 인증키 발급 확인

 

- https://www.vworld.kr/dev/v4dv_geocoderguide2_s001.do

 

브이월드

국가가 보유하고 있는 공개 가능한 공간정보를 모든 국민이 자유롭게 활용할 수 있도록 다양한 방법을 제공합니다.

www.vworld.kr

 

- 위의 사이트의 요청 URL 참고

https://api.vworld.kr/req/address?service=address&request=getCoord&key=[발급받은 인증키]&type=ROAD&address=[위치정보를 원하는 주소]

 

- 요청 파라미터

  service = address (필수)

  request = getCoord (필수)

  key = [발급받은 인증키] (필수)

  type: 검색 주소 유형 (필수)

    type = PARCEL : 지번 주소로 검색

    type = ROAD : 도로명 주소로 검색 (여기서는 도로명 주소로 검색함)

  address (필수)

    위치 정보를 원하는 주소 기입

    (여기서는 도로명 주소로 선택 후 대략적인 도로명 주소만 기입해도 가능함)

      refine = true (기본값)으로 설정되어 있기 때문

      대략적인 도로명 주소도 정제되어 완벽한 주소로 변환함

      ex) 인하대학교 > 인천광역시 미추홀구 인하로 100 (용현동)으로 정제됨

 

코드

◈ 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를 입력}"></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="@{/address}" method="post">
      <input name="address" placeholder="도로명 주소를 입력해주세요."/>

      <button type="submit">입력</button>
    </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>

 

 bundle.gradle

plugins {
	id 'java'
	id 'war'
	id 'org.springframework.boot' version '3.2.11'
	id 'io.spring.dependency-management' version '1.1.6'
}

group = 'com.project'
version = '0.0.1-SNAPSHOT'

java {
	toolchain {
		languageVersion = JavaLanguageVersion.of(17)
	}
}

configurations {
	compileOnly {
		extendsFrom annotationProcessor
	}
}

repositories {
	mavenCentral()
}

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-data-jdbc'
	implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
	implementation 'nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect'
	implementation 'org.springframework.boot:spring-boot-starter-web'
	implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:3.0.3'
	implementation 'org.apache.tika:tika-core:2.4.1'
	implementation 'org.apache.tika:tika-parsers:2.4.1'
	implementation 'net.coobird:thumbnailator:0.4.17'

	compileOnly 'org.projectlombok:lombok'
	developmentOnly 'org.springframework.boot:spring-boot-devtools'
	runtimeOnly 'com.mysql:mysql-connector-j'
	annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'
	annotationProcessor 'org.projectlombok:lombok'
//	providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
	testImplementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter-test:3.0.3'
	testRuntimeOnly 'org.junit.platform:junit-platform-launcher'

	// JSON 데이터를 처리하기 위한 라이브러리 추가
	implementation group: 'org.json', name: 'json', version: '20231013'
}

tasks.named('test') {
	useJUnitPlatform()
}

 

 WeatherService.java

package com.project.joonggo.service;

import org.json.JSONObject;
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
public class WeatherService {

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

        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 = ""; // Open Weather Map 날씨 API에서 발급받은 API 키 작성할 것

        // 넘어온 위도, 경도를 포함한 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;

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

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

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

 

출력

 

참고 자료 출처

- Geolocation API

https://velog.io/@sujung_shin/Geolocation-API로-현재-위치-가져오기-웹사이트에서-사용자-위치-기반-서비스-만들기

 

Geolocation API로 현재 위치 가져오기: 웹사이트에서 사용자 위치 기반 서비스 만들기

Geolocation API를 사용하여 웹사이트에서 사용자의 위치 정보를 가져오는 방법에 대해 알아보겠습니다. 이를 통해 위치 기반 추천 서비스나 날씨 정보 제공 등을 구현할 수 있습니다.Geolocation API는

velog.io

 

- Geocoder API 2.0 Key, Open Weather Map 날씨 API 

https://blogan99.tistory.com/146

 

[Spring Boot] 주소 입력으로 날씨 정보 출력 예제 프로젝트 (Geocoder API 2.0, open weather map API)

1. Geocoder API 2.0open weather map 의 현재 날씨 정보 API를 사용하기 위해서는 요청에 위도, 경도를 포함해야 함내 주소 (혹은 날씨를 알고 싶어하는 주소)의 위도, 경도를 알기 위해 사용함 1-1. Geocoder A

blogan99.tistory.com