본문 바로가기
프로젝트/RanDrive

기능 개선(새로운 랜덤 길찾기 알고리즘 프론트 코드 작성)

by HWK 2023. 11. 1.

프론트에 새로운 알고리즘 짜둔 방식을 적용시켰다.
알고리즘에 대한 설명은 아래 링크를 타고 들어가면 볼 수 있다.
https://hwk99.tistory.com/121

 

63일차 - 기능 개선(랜덤 길찾기 알고리즘)

랜덤 길찾기 알고리즘을 발전시킬 것이다. 지금의 알고리즘은 단지 20km이내에 있는 관광지를 무작위적으로 추천해주는 알고리즘이다. 그 결과 T자, 8자등 만든 사람도 별로 내키지 않는 경로를

hwk99.tistory.com

 

총 3개의 알고리즘이 나와서 이용자가 직접 어떤 방식의 길찾기를 사용할 것인지 고르게 할 것이다.
사용자는 탐색 범위, 경유지 수, 경로 타입을 직접 골라야 한다.
사용자들이 고르기 편하게 각각의 선택에 선택지를 줬다. 작성한 코드는 아래와 같고 randomdadius.js에 있다.

<div class="select-container">
    <label for="all-random-redius">탐색 범위</label><br>
    <select id="all-random-redius" name="all-random-redius">
        <option value="very-short">매우 짧게 (4km)</option>
        <option value="short">짧게 (10km)</option>
        <option value="moderate">적당히 (20km)</option>
        <option value="long">길게 (30km)</option>
        <option value="very-long">매우 길게 (40km)</option>
    </select>
</div>

<div class="select-container">
    <label for="all-random-waypoint-number">경유지 수</label>
    <select id="all-random-waypoint-number" name="all-random-waypoint-number">
        <option value="1">하나</option>
        <option value="2">둘</option>
        <option value="3">셋</option>
        <option value="5">많이 (5)</option>
        <option value="8">매우 많이 (8)</option>
    </select>
</div>

<div class="select-container">
    <label for="all-random-type">경로 타입</label>
    <select id="all-random-type" name="all-random-type">
        <option value="line">직선</option>
        <option value="zig-zeg">지그재그</option>
        <option value="circle">순환형</option>
    </select>
</div>

 

radius.js에서는 위에서 추가한 html 코드들을 작동할 수 있도록 해줘야 한다.
추가한 코드는 아래와 같고 각각의 메소드를 사용하면 사용자의 선택에 맞는 값을 반환해준다.

// 사용자가 반경을 옵션으로 선택 -----------------------------------------------------------------------------------------------//
const selectElement = document.getElementById("all-random-redius");
function getSelectedRadius() {
    const selectedValue = selectElement.value;
    switch (selectedValue) {
        case "very-short":
            return 4;
        case "short":
            return 10;
        case "moderate":
            return 20;
        case "long":
            return 30;
        case "very-long":
            return 40;
        default:
            return 10;
    }
}

// 원하는 경유지 수 받아옴 -----------------------------------------------------------------------------------------------//
const selectWaypointNumber = document.getElementById("all-random-waypoint-number");
function getSelectedWaypointNumber() {
    const selectedValue = selectWaypointNumber.value;
    switch (selectedValue) {
        case "1":
            return 1;
        case "2":
            return 2;
        case "3":
            return 3;
        case "5":
            return 5;
        case "8":
            return 8;
        default:
            return 1;
    }
}

// 원하는 경로 타입 지정 ------------------------------------------------------------------------------------------------//
function getSelectedRouteType() {
    const selectElement = document.getElementById("all-random-type");
    const selectedValue = selectElement.value;
    switch (selectedValue) {
        case "line":
            return "line";
        case "zig-zeg":
            return "zigzag";
        case "circle":
            return "circle";
        default:
            return "original"; 
    }
}

 

사용자가 길 찾기 버튼을 누르면 다음 코드가 실행된다.
추가해준 부분은 redius, waypointNum, secondeType이다.

// 사용자가 반경 길 찾기 버튼을 눌렀을 때
document.getElementById('all-random-search-form').addEventListener('submit', function(e) {
    e.preventDefault(); // 기본 submit 동작을 막습니다.

    const auth = getToken();

    if (auth !== undefined && auth !== '') {
        $.ajaxPrefilter(function (options, originalOptions, jqXHR) {
            jqXHR.setRequestHeader('Authorization', auth);
        });
    } else {
        window.location.href = host + '/view/user/login-page';
        return;
    }

    var originAddress = document.getElementById('all-random-originAddress').value;
    var redius = getSelectedRadius();
    var waypointNum = getSelectedWaypointNumber();
    var secondType = getSelectedRouteType();

    window.location.href = 'navi/' + 'live-all-random' + '/0/' + originAddress + '/route/' + redius + '/' + waypointNum + '/' + secondType;
});

또한 맨 마지막에 보면 주소에서 전달해주는 방식에 약간 변경이 된 것을 볼 수 있다.
window.location.href = 'navi/' + 'live-all-random' + '/0/' + originAddress + '/route/' + redius + '/' + waypointNum + '/' + secondType;

 

따라서 모든 주소의 형식을 바꿔줬다... @PathVariable를 정보전달하는데 의존적으로 사용한 결과이다. 다음부터는 지양하도록 하자.
navi.js의 처음 코드들도 아래와 같이 변경해줬다.

const type = segments[segments.length - 7];
const routeId = segments[segments.length - 6];
const originAddress = segments[segments.length - 5];
const destinationAddress = segments[segments.length - 4];
const redius = segments[segments.length - 3];
const waypointNum = segments[segments.length - 2];
const secondType = segments[segments.length - 1];

각각의 요청에서 secondeType에 따라 다른 결과가 나와야 한다.
고로 navi.js의 다음 메서드를 아래와 같이 secondType에 따라 다른 요청을 보내주도록 고쳤다.

function makeAllRandomNavi(originAddress, redius) {
    const auth = getToken();

    if (secondType === 'line') {
        fetch(`/api/line-all-random-route?originAddress=${originAddress}&distance=${redius}&count=${waypointNum}`, {
            method: 'GET',
            headers: {
                'Authorization': auth // 토큰을 Authorization 헤더에 실어 보냄
            }
        })
            .then(response => response.json())
            .then(data => {
                responseData = data;
                adapt_KakaoResponseToRouteData(data);
                makeLiveMap(data)
            })
            .catch(error => {
                console.error('All-Random-Error:', error);
            });
    }
    else if (secondType === 'zigzag') {
        fetch(`/api/zigzag-all-random-route?originAddress=${originAddress}&distance=${redius}&count=${waypointNum}`, {
            method: 'GET',
            headers: {
                'Authorization': auth // 토큰을 Authorization 헤더에 실어 보냄
            }
        })
            .then(response => response.json())
            .then(data => {
                responseData = data;
                adapt_KakaoResponseToRouteData(data);
                makeLiveMap(data)
            })
            .catch(error => {
                console.error('All-Random-Error:', error);
            });
    }
    else if (secondType === 'circle') {
        fetch(`/api/circle-all-random-route?originAddress=${originAddress}&distance=${redius}&count=${waypointNum}`, {
            method: 'GET',
            headers: {
                'Authorization': auth // 토큰을 Authorization 헤더에 실어 보냄
            }
        })
            .then(response => response.json())
            .then(data => {
                responseData = data;
                adapt_KakaoResponseToRouteData(data);
                makeLiveMap(data)
            })
            .catch(error => {
                console.error('All-Random-Error:', error);
            });
    }
    else {
        fetch(`/api/all-random-route?originAddress=${originAddress}&redius=${redius}`, {
            method: 'GET',
            headers: {
                'Authorization': auth // 토큰을 Authorization 헤더에 실어 보냄
            }
        })
            .then(response => response.json())
            .then(data => {
                responseData = data;
                adapt_KakaoResponseToRouteData(data);
                makeLiveMap(data)
            })
            .catch(error => {
                console.error('All-Random-Error:', error);
            });
    }
}


Controller에는 다음과 같은 코드들을 추가해줬다.

@GetMapping("/line-all-random-route")
@Operation(summary = "개선된 랜덤경로", description = "평탄한 랜덤 경로를 가져옵니다.")
public ResponseEntity<KakaoRouteAllResponseDto> getLineRandomWays(@RequestParam String originAddress, @RequestParam Integer distance,
                                                                  @RequestParam Integer count, @AuthenticationPrincipal UserDetailsImpl userDetails) {
    KakaoRouteAllResponseDto response = realRandomRouteSearchService.requestAllRandomWay(userDetails.getUser().getId(), originAddress, distance, count, 1);
    if (response == null) {
        return new ResponseEntity<>(HttpStatus.NO_CONTENT); // 적절한 HTTP 상태 코드로 응답
    }
    return new ResponseEntity<>(response, HttpStatus.OK);
}

@GetMapping("/zigzag-all-random-route")
@Operation(summary = "개선된 랜덤경로", description = "좀더 익스트림한 랜덤 경로를 가져옵니다.")
public ResponseEntity<KakaoRouteAllResponseDto> getZigzagRandomWays(@RequestParam String originAddress, @RequestParam Integer distance,
                                                                  @RequestParam Integer count, @AuthenticationPrincipal UserDetailsImpl userDetails) {
    KakaoRouteAllResponseDto response = realRandomRouteSearchService.requestAllRandomWay(userDetails.getUser().getId(), originAddress, distance, count, 2);
    if (response == null) {
        return new ResponseEntity<>(HttpStatus.NO_CONTENT); // 적절한 HTTP 상태 코드로 응답
    }
    return new ResponseEntity<>(response, HttpStatus.OK);
}

@GetMapping("/circle-all-random-route")
@Operation(summary = "개선된 랜덤경로", description = "순환형 랜덤 경로를 가져옵니다.")
public ResponseEntity<KakaoRouteAllResponseDto> getCircleRandomWays(@RequestParam String originAddress, @RequestParam Integer distance,
                                                                 @RequestParam Integer count, @AuthenticationPrincipal UserDetailsImpl userDetails) {
    KakaoRouteAllResponseDto response = realRandomRouteSearchService.requestAllRandomWay(userDetails.getUser().getId(), originAddress, distance, count, 3);
    if (response == null) {
        return new ResponseEntity<>(HttpStatus.NO_CONTENT); // 적절한 HTTP 상태 코드로 응답
    }
    return new ResponseEntity<>(response, HttpStatus.OK);
}

지금 보는 중에 각각의 코드가 같은 기능을 한다는 것을 알아챘다. 요청시 어떤 secondType인지 보내줘
if문을 통해 처리하도록 해서 코드를 좀더 깔끔하게 고쳐야겠다.
RealRandomRouteSearchService에는 다음과 같이 if문을 통해 다른 결과를 내게끔 코드를 수정했다.

public KakaoRouteAllResponseDto requestAllRandomWay(Long userId, String originAddress, Integer distance, Integer count, int type) {

    if (ObjectUtils.isEmpty(originAddress) || ObjectUtils.isEmpty(distance)) return null;

    // 출발지 주소를 좌표로 변환
    DocumentDto origin = kakaoAddressSearchService.requestAddressSearch(originAddress).getDocumentDtoList().get(0);
    double originY = origin.getLatitude();
    double originX = origin.getLongitude();

    double convertedDistance = distance / 100.0;

    // 목적지를 거리 기반으로 무작위적으로 가져오는 메소드
    DocumentDto destination = getDestination(originY, originX, convertedDistance);

    double destinationY = destination.getLatitude();
    double destinationX = destination.getLongitude();

    // 목적지 DB에 남김, 만일 동일 사용자가 이미 목적지를 저장해 놓았다면, 삭제
    RandomDestination randomDestination = new RandomDestination(userId, destination.getAddressName());
    RandomDestination olderRandomDestination = randomDestinationRepository.findByUserId(userId);
    if (olderRandomDestination != null)
        randomDestinationRepository.delete(olderRandomDestination);
    randomDestinationRepository.save(randomDestination);

    if (type == 1) {
        // 선형 랜덤 경유지 추천
        List<RandomDocumentDto> waypoints = getWayPointsAroundLine(originY, originX, destinationY, destinationX, count);
        return makeRequestForm(origin,destination,waypoints);
    }
    else if (type == 2) {
        // 박스형 랜덤 경유지
        List<RandomDocumentDto> waypoints = getWayPointsInBox(originY, originX, destinationY, destinationX, count);
        return makeRequestForm(origin,destination,waypoints);
    }
    else { // type == 3
        // 순환형 랜덤 경유지
        List<RandomDocumentDto> waypoints = getWayPointsCircular(originY, originX, convertedDistance/2, count);
        return makeRequestForm(origin, origin, waypoints);
    }
}

 

이로서 프론트 구현이 끝났다.
지금의 코드가 가독성이 낮지는 않지만, 코드를 더 정리해서 공간복잡도가 낮은 코드를 만들어야겠다.