샘성의 iOS 개발 일지

[KakaoMap 클론] 3. 지도 위에 경로 그리기 본문

iOS/UiKit

[KakaoMap 클론] 3. 지도 위에 경로 그리기

SamusesApple 2023. 5. 31. 20:16
728x90

목표 : 

  지도 위에 현재 위치에서 선택한 장소로 가는 자동차 경로 그리기

 

배경 :

  Alamofire pod install 된 상태, 카카오맵 SDK 설치 된 상태, map View 세팅 된 상태 (REST API 키 발급받은 상태)

 

 

 

 

1. 요청 파라미터 확인하기

 

카카오모빌리티 디벨로퍼스

카카오모빌리티 디벨로퍼스

developers.kakaomobility.com

 

  카카오맵을 클론하는 것이기에 경로를 받아오는 API까지... 카카오에서 제공하는 API를 사용할 것이다 ...!

 

 

 

 

  우선, 호스트와 Auth, content Type은 하단과 같다.

 

HOST: https://apis-navi.kakaomobility.com/v1/directions

Authorization : KakaoAK ${REST_API_KEY} - 카카오디벨로퍼스에서 발급 받은 REST API 키

Content-Type :	application/json

 

 

  필수 요청 파라미터로는 당연하게도, 출발지와 목적지의 좌표를 제공해야한다.

 

 

  이 외에도 경유, 경로 탐색 제한 옵션, 상세 도로 정보 제공 여부 등이 있지만 필자는 간단하게 '출발지'와 '목적지'의 좌표만 갖고 요청할 것이다.

 

 

  하단처럼 서버에 데이터 요청을 하게 될 것이다.

https://apis-navi.kakaomobility.com/v1/directions?origin=출발지위도,출발지경도&destination=목적지위도,목적지경도

*주의* 위도와 경도 사이에 문자열 따옴표(,)를 띄어쓰기 없이 붙일 것

 

 

 

 

 

2. Postman으로 샘플 데이터 받기 및 파싱할 구조체 만들기

  이제 파싱할 구조체 모델을 만들기 위한 샘플 데이터를 받기 위해, Postman을 통해 테스트 요청을 할 것이다.

 

origin : 출발점 (유저의 현재위치 좌표)

destination: 도착점  (유저가 가고자하는 장소 위치 좌표)

 

둘 다 기입할때 꼭 위도와 경도 사이에 , 넣는 것을 깜빡하면 안된다

 

 

Authorization에 개인의 카카오 Developer REST API키 값도 넣었다면, 요청을 넣으면 된다.

 

 

 

 

 요청이 성공적으로 되었다면, 서버로부터 result_msg에 "길찾기 성공"이라는 데이터가 포함된 상단의 사진과 같은 데이터(응답)를 받을 수 있다.

 

 

 

이제 응답 받은 데이터를 복사해서 하단의 링크에 붙여 넣으면 된다.

 

Instantly parse JSON in any language | quicktype

 

app.quicktype.io

 

 

 

 

[KakaoMap 클론] 2. Kakao API로 장소 검색 결과 받기

카카오맵 검색창에 '카페'라고 검색하면 사용자 위치 근처의 카페들에 대한 검색 결과를 보여준다. 이를 구현하기 위해 키워드로 장소 검색하기' 를 사용할 것이다. 배경: 카카오 REST API KEY 발급

iossammy.tistory.com

 

이 후의 내용은 사실 위의 글에도 작성했기에.... 참고하면 좋을 것 같다..!

 

 

 

 

 

 

 

3. 데이터 요청하기 (feat. 소스코드)

 

우선, 서버에 요청을 넣는 코드를 생성했다. 

* 요청 파라미터 생성 코드
 
   private func directionParameters(startLon: String, startLat: String, destinationLon: String, destinationLat: String) -> [String: Any] {
        [
            "origin": "\(startLon),\(startLat)",
            "destination": "\(destinationLon),\(destinationLat)"
        ]
    }

 

* 요청 코드

func getDirection(startLon: String, startLat: String, destinationLon: String, destinationLat: String, completion: @escaping(DestinationResult) -> Void) {
        let url = "https://apis-navi.kakaomobility.com/v1/directions"
        
        AF.request(url,
                   method: .get,
                   parameters: directionParameters(startLon: startLon,
                                                   startLat: startLat,
                                                   destinationLon: destinationLon,
                                                   destinationLat: destinationLat),
                   encoding: URLEncoding.default,
                   headers: headers)
        .validate(statusCode: 200..<600).responseDecodable(of: DestinationResult.self) { response in
            let result = response.result
            switch result {
            case.success(let direction):
            // 성공적으로 응답 받은 경우 - 받은 데이터를 completion블럭에 전달
                completion(direction)
            case .failure(let error):
                print(error)
            }
        }
    }

 

 

 

 

 

  그리고 필자는 MVVM 패턴으로 진행 중이며 ViewModel에서 네트워킹을 담당하도록 구현했기에, viewModel에서 경로를 요청하는 코드를 만들었다.

 

   * 현재 위치와, 목적지 위치 좌표로 서버에 요청하는 코드 실행하기

   /// 현재 위치에서 해당 장소로 이동하는 경로 알려주기
    func getDirection(completion: @escaping ([Guide]) -> Void) {
    	// 선택된 장소에 대한 데이터가 targetPlace 변수에 들어있음
        guard let targetPlace = targetPlace,
              let destinationLon = targetPlace.x, // 목적지 위도와 좌표 옵셔널 벗기기
              let destinationLat = targetPlace.y else { return }
        
        // 서버에 데이터 요청하기
        HttpClient.shared.getDirection(startLon: String(currentLongtitude),
                                       startLat: String(currentLatitude),
                                       destinationLon: destinationLon,
                                       destinationLat: destinationLat) { result in
            guard let routes = result.routes else {
                print("자동차 경로 없음")
                return
            }
            
            // alternative를 true로 하지 않았기에 routes 배열의 하나의 루트만 담겨있음
            guard let sections = routes[0].sections,
            	// 경유지 설정을 하지 않았기에 sections 배열에 하나의 데이터만 담겨있음
                  let guides = sections[0].guides else { return }
            // VC에서 경로를 그릴 것이기 때문에 경로가 담긴 데이터 넘기기
            completion(guides)
        }
    }

 

 

 

 

 

  이제 ViewController에서 ViewModel로부터 전달받은 데이터로, 선을 그릴 것이다.

하단의 코드는 단순히 데이터를 받아서 선을 그릴 수 있도록 하는 코드이다.

 * ViewModel로부터 받은 Guide 타입 데이터들로 poly line 그리는 함수
 
    private func makePolylines(guide: [Guide]) {
    // 목표 장소의 PoiItem을 제외한 모든 MapView 위에 있는 PoiItem 제거하기
        for item in mapView.poiItems {
            guard let item = item as? MTMapPOIItem,
                  let targetId = viewModel.targetPlace?.id else { return }
                  // poiItem의 tag를 장소의 placeId로 설정해놓았으므로 id 일치 경우 제거
            if String(item.tag) != targetId {
                mapView.remove(item)
            }
        }
        
        // polyLine을 그릴 MTMapPoint 배열 생성
        var mapPoints: [MTMapPoint] = []
        
        // polyLine 초기화
        polyLine = MTMapPolyline.polyLine()
        polyLine?.polylineColor = .blue // 선 색상 세팅
        
        for guide in guide {
            guard let lon = guide.x,  // 경로의 위도와 경도 옵셔널 벗기기
                  let lat = guide.y else {
                print("폴리라인 만드는 함수 - 옵셔널 벗기기 실패")
                return
            }
			// mapPoints 배열에 MTMapPoint 추가하기
            mapPoints.append(MTMapPoint(geoCoord: MTMapPointGeo(latitude: lat,
                                                                longitude: lon)))
        }
        // 선을 그릴 점들이 모인 배열(mapPoints) 추가하여 선 그리기
        polyLine?.addPoints(mapPoints)
        // mapView에 그린 선 추가하기
        mapView.addPolyline(polyLine)
        // polyLine이 다 보일 수 있도록 맵뷰 조정
        mapView.fitArea(toShow: polyLine)
    }

 

 

이제 해당 함수를, 버튼이 실행되는 함수에 넣어서 실행하면 된다.

* 네비게이션 버튼이 선택되면 실행되는 함수

	@objc private func navigationButtonTapped() {
        viewModel.getDirection { [weak self] guides in            
            self?.makePolylines(guide: guides)
        }
    }

 

 

 

 

 

 

4. 결과 

 

선의 색이 좀 맘에 안들지만.... 수정하는건 간단하니까 ㅎㅎ...

경로 선 그리기 끝-

728x90