[Spring] 주소, 위도/경도 처리

dsunni·2020년 6월 28일
5

SSGSO-Project

목록 보기
1/2
post-thumbnail

🗺️

SSGSO 프로젝트를 진행하며 숙소의 전반적인 개발을 맡았는데 그 중 가장 많은 시간을 할애하고 고민을 한 부분이 숙소 생성이었다.

단순히 Jsp Form에서 사용자의 input값을 넘겨주었던 과거의 프로젝트와는 달리 이번에는 도로명 주소 API를 활용해 주소를 받아오고, 또 받아온 주소를 원하는 형태(위도/경도)로 적절하게 바꾸고 mapper로 넘겨줘야했기 때문이다.

따라서 이번 글에서는 다음의 과정들을 기록하고자한다.

  1. 도로명 주소 팝업 API
  2. 카카오 API 지오코딩 (Geocoding)
  3. Response에서 x,y 값을 추출
  4. Oracle DB에 INSERT & SELECT

사실 3번 부분이 많이 비효율적인거 같다(수많은 TypeReference의 사용..)

객체를 만드는게 더 효율적인 방법이었을까? 아니면 다른 좋은 방법이 또 있을까? 아직도 많이 고민된다.🤔


DB에 들어갈 data

  • 전체 주소
    • 도로명주소 API 활용
  • 위도, 경도
    • 얻은 전체 주소 활용
    • Kakao Geocoder API

✔️Github

1. 도로명 주소 팝업 API로 전체 주소 얻어오기

(1) 팝업 API 소스 다운

https://www.juso.go.kr/addrlink/devAddrLinkRequestGuide.do?menu=roadApi

  1. JSP를 사용할 예정이라 가이드 및 소스 다운로드 JSP를 선택
  2. 다운받은 파일을 압축해제하면 가이드 파일과 팝업창, 팝업창을 사용하는 샘플 페이지가 들어있다.
    • 가이드.pdf
    • jusoPopop.jsp
    • Sample.jsp

(2) API 사용 신청하기

  1. 좌측 하단의 API 신청하기 클릭
  2. 업체(기관명) : 프로젝트 이름
  3. URL : Context Path
    • 포트는 80번, Context Path는 ssgso로 설정되어있음
  4. 핸드폰 인증
  5. 완료
  6. 승인키에서 API 호출을 위한 키를 확인할 수 있다. 복사해두기!!

3. Sample과 jusoPopup 사용

  • jusoPopup.jsp 파일을 popup 폴더안에 넣고 폴더 자체를 프로젝트 안에 복사

jusoPopup.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <title>Insert title here</title>
      String roadFullAddr = request.getParameter("roadFullAddr"); 
  </head>
  <script language="javascript">
      
    function init() {
      var url = location.href;
      var confmKey = "발급받은 승인키";
        ...
    }
  • 전체 주소만 얻어오면되기 때문에 roadFullAddr 만 남기고 다 주석처리하기
  • confmKey에 승인키 입력

form.jsp

<div class="secRight">
    <div class="rightheader">숙소 등록</div>
    <form name="form" action="<%=contextPath%>/mypage/mySsgsoCreate" method="post">
        <table class="list">
            <tr>
                <th>주소</th>
                <td>
                    <input type="text" id="roadFullAddr" style="width:80%;" name="roadFullAddr" placeholder="주소검색을 눌러 주소를 입력해주세요" required readonly />
                </td>
            </tr>
            <tr>
                <td colspan="2">
                    <input type="submit" value="등록" id="addbutton">
                    <input type="reset" value="초기화" id="resetbutton">
                </td>
            </tr>
        </table>
    </form>
    <button class="searchButton" onclick="goPopup()">주소 검색</button>
</div>
  • 확인해야할 것들
    1. form name
    2. input id
    3. popup 경로

📌

  • 그리고 꼭 팝업 버튼은 form 태그 바깥에 빼주어야한다. jusoPopup도 form 형태로 가기 때문에 값이 제대로 입력되지 않은 채로 Controller로 바로 전달되기 때문이다.

실행했을 때 자꾸 jsp 파일을 찾을 수 없다고 떠서 많은 애를 먹었다.🤦🏻‍♀️

경로를 상대경로로도 해보고, 절대경로로도 해보고, 현재 폴더 위치로 바꿔보기도하고 온갖 경로로 바꿔봐도 나오지가 않아서 아예 폴더 자체를 이리저리 옮기다보니 팝업이 정상적으로 나타났다.

아래와 같이 webapp 바로 아래에 popup을 넣어주면

view 아래 mypage 폴더에 위치한 jsp와 "../popup/jusoPopup.jsp" URL로 적용할 수 있다.


2. 지오코딩 (Geocoding)

지오코딩이란 고유 명칭(주소 등)을 가지고 위도와 경도의 좌표값을 얻는 것을 의미한다.

  • Kakao Geocode API 활용

(1) Kakao 지도 API키 발급 & Test


(2) Postman에서 GET request 테스트

https://dapi.kakao.com/v2/local/search/address.json?query=판교역로 235
  1. GET & 테스트용 Request url 지정
  2. Headers
    • KEY : Authorization
    • VALUE : KakaoAK 승인키붙여넣기
      • 승인키에는 REST API키 / Admin키 사용 가능
  3. 정상적으로 값이 오는걸 확인할 수 있다.


(3) Spring에서 Kakao API 호출하기

@Override
public String getKakaoApiFromAddress(String roadFullAddr) {
    String apiKey = "승인키";
    String apiUrl = "https://dapi.kakao.com/v2/local/search/address.json";
    String jsonString = null;

    try {
        roadFullAddr = URLEncoder.encode(roadFullAddr, "UTF-8");

        String addr = apiUrl + "?query=" + roadFullAddr;

        URL url = new URL(addr);
        URLConnection conn = url.openConnection();
        conn.setRequestProperty("Authorization", "KakaoAK " + apiKey);

        BufferedReader rd = null;
        rd = new BufferedReader(new InputStreamReader(conn.getInputStream(), "UTF-8"));
        StringBuffer docJson = new StringBuffer();

        String line;

        while ((line=rd.readLine()) != null) {
            docJson.append(line);
        }

        jsonString = docJson.toString();
        rd.close();

    } catch (UnsupportedEncodingException e) {
        e.printStackTrace();
    } catch (MalformedURLException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
    return jsonString;
}
  • 원래는 RestTemplate을 사용하려고 했다. 하지만 Spring Boot에서는 잘만 사용할 수 있었던 ResponseEntity와 같은 것들을 Spring Framework에서는 사용할 수 없었다.🤦

    • (ex. HttpEntity 등...)
    • 다음 포스팅에서는 Spring Framework와 Spring Boot에서의 HTTP 통신 템플릿을 정리해야겠다.
  • 또한 Header에 정보를 담아 보내야 하는 형태여서 getObject 등을 사용하지 못했다.

  • 그 대신 URLConnection을 사용했다.

  • 우선 URLEncoder을 이용해 roadFullAddr을 UTF-8로 인코딩했다.

    • 한글 때문에 글자가 깨질 수가 있어서 인코딩이 필요하다.
  • URL에 API 호출 주소를 넣고 setRequestProperty으로 헤더 Authorization을 추가

  • StringBuffer에 값을 넣고 String 형태로 변환하고 jsonString을 return


URLConnection

https://sjh836.tistory.com/141

jdk 1.2 부터 내장되어 있으며, java.net 패키지에 있다. URL의 내용을 읽어오거나, URL 주소에 GET, POST로 데이터를 전달 할 때 사용한다.

  • 단점
    • 응답코드가 4xx / 5xx 일때 IOException 발생
    • 타임아웃 설정 불가
    • 쿠키 제어 불가

3. JSON String → Map 변환하고 x,y 값 얻기

이번에 String 으로 된 JSON 을 변경하는게 좋은 방법이 아니라는 점을 알게되었다. (이유를 찾아봐도 안나와서 직접 Stackoverflow에 질문해놨다.. 답변 plz..ㅜ_ㅜ)

내 코드가 많이 복잡해지고 애를 먹었던 이유이지 않을까 싶다. 좀더 좋은 방법이 무엇일까.. 이럴때 정말 좋은 선생님, 또는 좋은 멘토의 중요성을 절실히 느낀다🤧

Json String과 Map 사이의 변환은 Jackson 라이브러리를 사용했다.

🙇🏻‍♀️ 답변이 왔다!

답변에 따르면 JSON → Map 으로 바꾸는것은 Javascript나 Python에서만 하는 방법이므로

Java에서는 관용적으로 JSON과 매핑되는 객체를 설계하고 연결해준다고 한다.

역시 객체를 생성해서 매핑시켜줘야 했나보다!! 다음주부터 프로젝트를 이어서 할 예정인데 그때 이 부분을 리팩토링해야겠다🤸🏼‍♀️🤸🏼‍♀️🤸🏼‍♀️ 아이씬나

(1) Jackson 라이브러리 추가

<dependency> 
    <groupId>com.fasterxml.jackson.core</groupId> 
    <artifactId>jackson-databind</artifactId> 
    <version>2.9.8</version> 
</dependency>

(2) JSON String → Map

ObjectMapper mapper = new ObjectMapper();

TypeReference<Map<String, Object>> typeRef = new TypeReference<Map<String, Object>>(){};

Map<String, Object> jsonMap = mapper.readValue(jsonString, typeRef);

(3) ServiceImpl.java

@Override
public HashMap<String, String> getXYMapfromJson(String jsonString) {
    ObjectMapper mapper = new ObjectMapper();
    HashMap<String, String> XYMap = new HashMap<String, String>();

    try {
        TypeReference<Map<String, Object>> typeRef 
            = new TypeReference<Map<String, Object>>(){};
        Map<String, Object> jsonMap = mapper.readValue(jsonString, typeRef);

        @SuppressWarnings("unchecked")
        List<Map<String, String>> docList 
            =  (List<Map<String, String>>) jsonMap.get("documents");	

        Map<String, String> adList = (Map<String, String>) docList.get(0);
        XYMap.put("x",adList.get("x"));
        XYMap.put("y", adList.get("y"));

    } catch (JsonParseException e) {
        e.printStackTrace();
    } catch (JsonMappingException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
    return XYMap;
}
  • JSON String에서 총 세번을 변환시켜서 x, y 값을 얻었다. (국회의사당역 주소 검색)

    1. jsonString

      {"documents":[{"address":{"address_name":"서울 영등포구 여의도동 1-6","b_code":"1156011000","h_code":"1156054000","main_address_no":"1","mountain_yn":"N","region_1depth_name":"서울","region_2depth_name":"영등포구","region_3depth_h_name":"여의동","region_3depth_name":"여의도동","sub_address_no":"6","x":"126.917885535023","y":"37.5280674292228"},"address_name":"서울 영등포구 국회대로 지하 758","address_type":"ROAD_ADDR","road_address":{"address_name":"서울 영등포구 국회대로 지하 758","building_name":"국회의사당역","main_building_no":"758","region_1depth_name":"서울","region_2depth_name":"영등포구","region_3depth_name":"여의도동","road_name":"국회대로","sub_building_no":"","underground_yn":"Y","x":"126.917885535023","y":"37.5280674292228","zone_no":"07236"},"x":"126.917885535023","y":"37.5280674292228"}],"meta":{"is_end":true,"pageable_count":1,"total_count":1}}
    2. jsonMap

      • JSON String을 jsonMap으로 변환
      {documents=[{address={address_name=서울 영등포구 여의도동 1-6, b_code=1156011000, h_code=1156054000, main_address_no=1, mountain_yn=N, region_1depth_name=서울, region_2depth_name=영등포구, region_3depth_h_name=여의동, region_3depth_name=여의도동, sub_address_no=6, x=126.917885535023, y=37.5280674292228}, address_name=서울 영등포구 국회대로 지하 758, address_type=ROAD_ADDR, road_address={address_name=서울 영등포구 국회대로 지하 758, building_name=국회의사당역, main_building_no=758, region_1depth_name=서울, region_2depth_name=영등포구, region_3depth_name=여의도동, road_name=국회대로, sub_building_no=, underground_yn=Y, x=126.917885535023, y=37.5280674292228, zone_no=07236}, x=126.917885535023, y=37.5280674292228}], meta={is_end=true, pageable_count=1, total_count=1}}
    3. docList

      • jsonMap를 documents Object으로 변환
      [{address={address_name=서울 영등포구 여의도동 1-6, b_code=1156011000, h_code=1156054000, main_address_no=1, mountain_yn=N, region_1depth_name=서울, region_2depth_name=영등포구, region_3depth_h_name=여의동, region_3depth_name=여의도동, sub_address_no=6, x=126.917885535023, y=37.5280674292228}, address_name=서울 영등포구 국회대로 지하 758, address_type=ROAD_ADDR, road_address={address_name=서울 영등포구 국회대로 지하 758, building_name=국회의사당역, main_building_no=758, region_1depth_name=서울, region_2depth_name=영등포구, region_3depth_name=여의도동, road_name=국회대로, sub_building_no=, underground_yn=Y, x=126.917885535023, y=37.5280674292228, zone_no=07236}, x=126.917885535023, y=37.5280674292228}]
      
    4. adList

      • docList를 address Map으로 변환하고
      • adList.get("x"), adList.get("y")으로 값을 얻어왔다.
      {address={address_name=서울 영등포구 여의도동 1-6, b_code=1156011000, h_code=1156054000, main_address_no=1, mountain_yn=N, region_1depth_name=서울, region_2depth_name=영등포구, region_3depth_h_name=여의동, region_3depth_name=여의도동, sub_address_no=6, x=126.917885535023, y=37.5280674292228}, address_name=서울 영등포구 국회대로 지하 758, address_type=ROAD_ADDR, road_address={address_name=서울 영등포구 국회대로 지하 758, building_name=국회의사당역, main_building_no=758, region_1depth_name=서울, region_2depth_name=영등포구, region_3depth_name=여의도동, road_name=국회대로, sub_building_no=, underground_yn=Y, x=126.917885535023, y=37.5280674292228, zone_no=07236}, x=126.917885535023, y=37.5280674292228}
  • 정말 다시봐도 엄청난 노가다다...^^...🤦 차라리 객체를 생성해서 넣어두었어야 했을까? 아니면 서버단에서 이걸 처리한게 실수일까?

  • JSONObject을 사용하려고 했지만 자꾸 arguments 에러가 떠서 사용하지 못했다.



4. Oracle DB에 위도 경도 INSERT하기

x(경도, longitude)와 y(위도, latitude)값은 실수 형태이다.

  • x=126.917885535023, y=37.5280674292228

따라서

  1. 소수점 형태로 DB에 값을 INSERT
  2. 소수점 형태 SELECT 해와야 한다.

(1) Oracle DB 실수형 INSERT (Number 타입 사용)

NUMBER(p, c)

  • p : 소수점을 포함한 모든 숫자의 개수
  • c는 소수점이하 숫자의 개수
  • NUMBER(10,6)
    • 모든 숫자는 10자리, 정수는 3자리,
    • 소숫점 아래 6자리까지 표현될 수 있다.
      • 37.5280674292228 → 37.528067
      • 123.917885535023 → 123.917886

(2) 소수점 형태로 SELECT

TO_CHAR(data, 'FM990.000000')

  • 9 : 해당자리 숫자 의미, 없을 경우 공백
  • 0 : 해당 자리 숫자, 없을 경우 0표시
  • `FM : 좌우 공백 제거
<select id="selectAccomodation" parameterType="int" resultType="com.project.ssgso.dto.AccomodationDto">
    SELECT AC_NO, NAME, INFO, AC_IMG, 
    TO_CHAR(LATITUDE, 'FM990.000000') AS latitude, 
    TO_CHAR(LONGITUDE, 'FM990.000000') AS longitude, 
    ADDRESS, AC_PHONE, AC_ACCOUNT, AC_PRICE, CATEGORY, MEM_NO
    FROM ACCOMODATION
    WHERE AC_NO = #{ac_no}
    ORDER BY AC_NO ASC
</select>
profile
https://dsunni.tistory.com/ 이사갑니답

1개의 댓글

comment-user-thumbnail
2020년 6월 29일

개발자 국비과정을 듣게 됐는데 스프링 프로젝트 할 때, 참고해봐야겠네요~감사합니다~

답글 달기