프로젝트를 하는데 식당 정보를 입력하면 거기 위치에 맞는 핀을 찍어줘야 하는데, 핀을 찍기 위해서는 위,경도가 필요했다.
하지만 식당 정보를 입력할때 위,경도를 입력하는 경우는 거의 드물기 때문에 입력란에 있는 도로명주소를 통해서 위,경도를 찾은 후에 데이터베이스에 넣는 작업을 진행해보고자 했다.
다행히도 kakao, naver등에서 도로명주소를 위,경도로 변환해주는 api가 존재했는데,
https://developers.kakao.com/docs/latest/ko/local/dev-guide#address-coord
여기에 자세한 설명이 있다. 간단하게 말하면 https://dapi.kakao.com/v2/local/search/address.json 에 자신의 apikey를 header의 Authorization에 입력해서 호출을 하면 된다.
@Value("${KAKAO_API_KEY}")
String apiKey;
@Value("${KAKAO_URL}")
String apiUrl;
나는 일단 코드를 git에 올려야했기 때문에 보안상에 문제가 있는 것들을 application.properties에 넣어서 관리했다.
fullAddr = URLEncoder.encode(fullAddr, "UTF-8");
String addr = apiUrl + "?query=" + fullAddr;
URL url = new URL(addr);
URLConnection conn = url.openConnection();
conn.setRequestProperty("Authorization", "KakaoAK " + apiKey);
이런식으로 코드를 작성하면 api를 불러올 수 있는데 이 return format이 json형식으로 나온다.
// https://dapi.kakao.com/v2/local/search/address.json?query=대구광역시 북구 대학로 80
{
"documents": [
{
"address": {
"address_name": "대구 북구 산격동 1370-1",
"b_code": "2723011100",
"h_code": "2723063000",
"main_address_no": "1370",
"mountain_yn": "N",
"region_1depth_name": "대구",
"region_2depth_name": "북구",
"region_3depth_h_name": "산격3동",
"region_3depth_name": "산격동",
"sub_address_no": "1",
"x": "128.614322336303",
"y": "35.8890974884948"
},
"address_name": "대구 북구 대학로 80",
"address_type": "ROAD_ADDR",
"road_address": {
"address_name": "대구 북구 대학로 80",
"building_name": "경북대학교",
"main_building_no": "80",
"region_1depth_name": "대구",
"region_2depth_name": "북구",
"region_3depth_name": "산격동",
"road_name": "대학로",
"sub_building_no": "",
"underground_yn": "N",
"x": "128.614322336303",
"y": "35.8890974884948",
"zone_no": "41566"
},
"x": "128.614322336303",
"y": "35.8890974884948"
}
],
"meta": {
"is_end": true,
"pageable_count": 1,
"total_count": 1
}
}
python의 경우에는 json형식을 받아오는 라이브러리가 존재하기 때문에 손쉽게 parsing을 할 수 있었는데, java의 경우는 한번도 시도해보지 않아서 일단 buffer에 담아서 jsonstring으로 만들어 놓은 다음에 parsing을 따로 진행해보기로 했다.
BufferedReader json = null;
json = new BufferedReader(new InputStreamReader(conn.getInputStream(), "UTF-8"));
StringBuffer docJson = new StringBuffer();
String line;
while ((line = json.readLine()) != null) {
docJson.append(line);
}
jsonString = docJson.toString();
json.close();
spring에도 json.simple이라는 라이브러리가 존재했는데, 내장 라이브러리가 아니기 때문에 다운을 따로 받아서 사용해야 했다. 사용하는 방법은 다른 사이트에도 많이 있기 때문에 따로 넣어놓지는 않겠다.
{ -> 1번
"documents": [ -> document찾기 2번
{ -> 첫번째 index의 json찾기 3번
"address": {
...
},
... -> key=x,y인 요소 찾기 4번
"x": "128.614322336303",
"y": "35.8890974884948"
}
],
"meta": {
...
}
}
JSONParser()는 쉽게 말하면 json 형태를 인식 즉, 중괄호를 확인하는 메소드라고 보면 된다. 그리고 JSONParser().parse()를 이용해서 중괄호를 벗겨내고 key=documents인 배열을 찾고, 그 배열안에 가장 첫번째 index의 json의 key가 x, y인 요소를 찾을 수 있었다.
ArrayList<Float> array = new ArrayList<Float>();
JSONParser parser = new JSONParser();
JSONObject document = (JSONObject)parser.parse(jsonString); -> 1번
JSONArray jsonArray = (JSONArray) document.get("documents"); -> 2번
JSONObject position = (JSONObject)jsonArray.get(0);->3번
float lon = Float.parseFloat((String) position.get("x")); -> 4번
float lat = Float.parseFloat((String) position.get("y")); -> 4번
array.add(lon);
array.add(lat);
return array;
이 코드는 service에 저장되어있던 코드이기 때문에 controller에서 따로 호출해서 사용했다.
public class GeoService {
@Value("${KAKAO_API_KEY}")
String apiKey;
@Value("${KAKAO_URL}")
String apiUrl;
public String getKakaoApiFromAddress(String fullAddr) {
String jsonString = null;
try {
fullAddr = URLEncoder.encode(fullAddr, "UTF-8");
String addr = apiUrl + "?query=" + fullAddr;
URL url = new URL(addr);
URLConnection conn = url.openConnection();
conn.setRequestProperty("Authorization", "KakaoAK " + apiKey);
BufferedReader json = null;
json = new BufferedReader(new InputStreamReader(conn.getInputStream(), "UTF-8"));
StringBuffer docJson = new StringBuffer();
String line;
while ((line = json.readLine()) != null) {
docJson.append(line);
}
jsonString = docJson.toString();
json.close();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return jsonString;
}
public ArrayList changeToJSON(String jsonString) throws ParseException {
ArrayList<Float> array = new ArrayList<Float>();
JSONParser parser = new JSONParser();
JSONObject document = (JSONObject)parser.parse(jsonString);
JSONArray jsonArray = (JSONArray) document.get("documents");
JSONObject position = (JSONObject)jsonArray.get(0);
float lon = Float.parseFloat((String) position.get("x"));
float lat = Float.parseFloat((String) position.get("y"));
array.add(lon);
array.add(lat);
return array;
}
}
front에서 post요청을 하게 되면 거기에서 들어온 address를 받아서 위, 경도로 알아서 바꿔줄 수 있도록 했다. address가 도로명주소가 아닐때는 오류가 나는데, front쪽에서 무조건 도로명주소로 들어갈 수 있도록 했기 때문에 따로 exception을 처리해주지는 않았다.
public Restaurant create(@RequestBody RestaurantInfo restaurantInfo) throws ParseException {
String addr = restaurantInfo.getAddress();
String json = geoService.getKakaoApiFromAddress(addr);
ArrayList<Float> pos = geoService.changeToJSON(json);
restaurantInfo.setLon(pos.get(0));
restaurantInfo.setLat(pos.get(1));
...
return restaurantInfo;
}