문제 상황
- 현재 프로젝트에서는 일주일 이내의 단기 일정에 대한 추천을 AI 서버에서 받아오고 있습니다.
- AI 서버에서는 출발지를 기준으로 설정된 클러스터 내에서 사용자 선호도에 적합한 여행지들을 뽑아서 보내주고 있습니다.
- 만약 AI 서버에서 3일치의 결과를 뽑는다면 클러스터 내에서 가장 만족스러운 결과를 1일 당 5개의 장소씩 총 15개를 뽑아서 주고 있습니다.
- AI 서버에서는 지정한 클러스터 내에서 만족하는 결과가 원하는 개수만큼 안나올 수도 있기 때문에 클러스터를 넓게 지정했지만 결과가 너무 넓게 나오고 있습니다.
- 하지만 이는 사용자 경로가 고려되지 않고 있기 때문에 경로를 최적화 해줘야 사용자 경험이 올라갈 수 있다고 생각하여 이를 최적화해보려 합니다!
문제 상황 확인
시각화
- 현재의 결과 화면
- 맵에 마커 표시
- 2번과 3번의 거리가 가장 먼 것을 확인할 수 있음
- 해당 결과를 보면 거리가 전혀 고려되지 않고 있다는 것을 확인할 수 있습니다.
- 클러스터가 너무 넓어서 위와 같은 결과가 나오지만 이는 AI 추천 서버에서 조정할 문제이고 저는 우선 최단 경로를 짜주려고 합니다!
문제 해결 아이디어
1. 노드 간 거리 구하기
Google Distance Matrix API를 사용하여 위치 간 거리 계산하기
-
우선 Google Distance Matrix API를 사용하여 두 노드 간의 거리를 계산해보고 데이터의 신뢰성을 확인해보았습니다!
-
코드 구성
private int[][] computeDistanceMatrix(List<PlaceDTO> places) {
int n = places.size();
int[][] dist = new int[n][n];
// 각 경유지 간의 거리 계산
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (i != j) {
dist[i][j] = (int) getDistanceBetweenPlaces(places.get(i), places.get(j)); // 거리 계산
count += 1;
} else {
dist[i][j] = 0; // 동일한 노드 간의 거리는 0
}
}
}
return dist;
}
public float getDistanceBetweenPlaces(PlaceDTO place1, PlaceDTO place2) {
String origins = place1.getLatitude() + "," + place1.getLongitude();
String destinations = place2.getLatitude() + "," + place2.getLongitude();
String url = "https://maps.googleapis.com/maps/api/distancematrix/json?"
+ "units=metric&mode=transit"
+ "&origins=" + origins
+ "&destinations=" + destinations
+ "®ion=KR"
+ "&key=" + googleApiKey;
RestTemplate restTemplate = new RestTemplate();
Map<String, Object> response = restTemplate.getForObject(url, Map.class);
// 응답에서 거리 값을 추출, Null 체크 포함
List<Map<String, Object>> rows = (List<Map<String, Object>>) response.get("rows");
if (rows != null && !rows.isEmpty()) {
Map<String, Object> row = rows.get(0);
List<Map<String, Object>> elements = (List<Map<String, Object>>) row.get("elements");
if (elements != null && !elements.isEmpty()) {
Map<String, Object> element = elements.get(0);
Map<String, Object> distance = (Map<String, Object>) element.get("distance");
if (distance != null && distance.get("value") != null) {
return ((Number) distance.get("value")).floatValue(); // 거리 값 (미터 단위)
} else {
log.error("Distance is null for the request between " + place1.getName() + " and " + place2.getName());
log.info(place1.toString());
log.info(place2.toString());
}
}
}
return -1; // 거리를 계산할 수 없을 때
}
{destination_addresses=[108 Makgye-dong, Gwacheon-si, Gyeonggi-do, South Korea], origin_addresses=[495-86 Sangam-dong, Mapo-gu, Seoul, South Korea], rows=[{elements=[{distance={text=24.3 km, value=24264}, duration={text=1 hour 16 mins, value=4561}, status=OK}]}], status=OK}
Distance between 난지한강공원 and 서울랜드: 24264.0 meters
- 난지 한강 공원과 서울 랜드 사이의 거리는 24.3km, 대중교통 이용 시 소요 시간은 1시간 16분으로 나왔습니다.
- 내가 자주 사용하는 네이버 지도에서 더블 체크해보았습니다.
- 대중교통 이용 시 소요시간과 유사하게 나왔습니다.
- 이로써 데이터의 신뢰성은 확보했습니다!
각 노드 간 거리 구하기에서의 문제 발생
- 이제 문제 상황에 대해 알아보겠습니다.
문제 1. 너무 가까운 거리는 거리를 계산할 수 없음
- 현재 API에서 값을 반환해주지 않을 때 아래와 같이 로그를 출력하게 했습니다.
log.error("Distance is null for the request between " + place1.getName() + " and " + place2.getName());
log.info(place1.toString());
log.info(place2.toString());
- 실행하면 꼭 한 두개씩은 아래와 같은 오류가 발생했습니다.
- 현재 거리를 transit으로 구하고 있어서 너무 가까운 거리나 대중교통으로 갈 수 없는 곳은 API 반환이 안되고 있는 문제였습니다.
- 거리를 구할 수 없을 때는 거리가 매우 가깝다고 가정하여 이를 -1로 두고 진행하였습니다.
return -1; // 거리를 계산할 수 없을 때
문제 2. 너무 많은 API를 호출한다.
-
15개의 장소가 주어지고 출발지까지 16개의 장소가 있을 때 API를 420번 요청하고 있습니다.
-> n * (n - 1)
문제 3. 너무 많은 요청때문에 비용이 너무 많이 발생함
- 위의 문제점과 유사한 문제인데 API를 너무 많이 요청하고 있어서 비용이 너무 많이 발생한다는 문제가 있습니다.
- 현재는 무료 평가판을 사용하고 있지만 일주일 뒤 체험판이 종료되면 요금 폭탄 예상됩니다…
정리
- 현재 해결해야 할 문제들을 본다면 다음과 같습니다.
1. 너무 가까운 거리는 계산할 수 없다.
2. API호출이 너무 많고 이 때문에 비용이 너무 많이 발생한다.
- 다음 포스트에서는 위의 문제를 해결할 방법들을 찾아보도록 하겠습니다!!