[iOS] NaverMap API - Reverse Geocoding

Reverse Geocoding이란?

 

특정 위도와 경도를 이용해서 해당 위치에 대한 주소를 구하는 것을 말한다.

네이버 맵을 사용할 경우 네이버 API를 사용해 쉽게 구현할 수 있다.

 

 

 

 

Reverse Geocoding API

etc-image-0

 

Reverse Geocoding 역시 get 메서드로 요청을 하며

Header클라이언트 ID시크릿 키를 넣어줘야 한다.

 

Reverse Geocoding은 좌표를 주소로 변환하는 API이기에

저번 글에서 만든 마커를 가운데에 고정시킨 지도 객체를 활용해 좌표값을 받아보겠다.

 

 

[iOS] NaverMap - 화면 가운데 고정된 마커 설정 및 좌표 반환

이번 글은 프로젝트를 진행하며 내가 알아내고 구현한 것 위주로 서술하였다. 다른 더 좋은 방법이 있을 수도 있다!   화면 가운데 마커 설정하기택시 호출 앱 등을 살펴보면 화면 정 가운데

d0ngurrrrrrr.tistory.com

 

 

 

 

 

코드

우선 네트워크 통신 메서드부터 만들어보자면

    func callRequest(coords: String, completion: @escaping (Data) -> Void) {
        
        guard var urlComponents = URLComponents(string: "https://naveropenapi.apigw.ntruss.com/map-reversegeocode/v2/gc") else {
            print("URL Components Error")
            return
        }
        
        let queryItemArray = [
            URLQueryItem(name: "coords", value: coords),
            URLQueryItem(name: "orders", value: "roadaddr"),
            URLQueryItem(name: "output", value: "json")
        ]
        urlComponents.queryItems = queryItemArray
        
        guard let url = urlComponents.url else {
            print("URL Error")
            return
        }
        
        var urlRequest = URLRequest(url: url)
        
        urlRequest.addValue("클라이언트 Id", forHTTPHeaderField: "X-NCP-APIGW-API-KEY-ID")
        urlRequest.addValue("Secret 키", forHTTPHeaderField: "X-NCP-APIGW-API-KEY")
        
        URLSession.shared.dataTask(with: urlRequest) { data, response, error in
            
            guard let data = data, let response = response as? HTTPURLResponse, response.statusCode == 200 else {
                print("status code error")
                return
            }
            completion(data)
        }.resume()
    }

 

 

Geocoding과 비슷하게 만들 수 있다.

이때 차이점은 쿼리에 output과 coords, 즉 좌표값을 줘야 한다는 것이다.

output을 json으로 주지 않으면 xml로 오기 때문에 xml로 파싱을 할 것이 아니라면 json으로 할 것을 추천한다.

 

 

    // 딜리게이트 메서드
    func mapViewCameraIdle(_ mapView: NMFMapView) {
        print(mapView.cameraPosition.target)
        let coords = "\(mapView.cameraPosition.target.lng)" + "," + "\(mapView.cameraPosition.target.lat)"
        callRequest(coords: coords) { data in
            do {
                let decodeData = try JSONDecoder().decode(ReverseGeocodingModel.self, from: data)
                print(decodeData)
            } catch {
                print(error)
            }
        }
    }
    
//모델
struct ReverseGeocodingModel: Decodable {
    let results: [Address]
}
 
struct Address: Decodable {
    let region: Region
    let land: Land
}
 
struct Region: Decodable {
    let area1: Area1
    let area2, area3, area4: Area
}
 
struct Area1: Decodable {
    let name, alias: String
}
 
struct Area: Decodable {
    let name: String
}
 
struct Land: Decodable {
    let number1, number2: String
    let addition0: Addition0
    let name: String?
}
 
struct Addition0: Decodable {
    let type, value: String
}

 

 

이제 callRequest를 호출해야 하는데, 언제 이 메서드를 호출하느냐가 중요하다.

나는 mapViewCameraIdle 메서드에서 호출을 했다.

 

그 이유는 간단하다.

카메라 이동의 모든 움직임이 종료되었을 때, 호출을 해서 call 수를 줄일 수 있기 때문이다.

mapViewCameraIdle이 아니라 cameraIsChangingByReason, cameraDidChangeByReason에서 호출을 하게 되면

드래그를 하는 동안에도 여러 번 호출이 될 수 있다.

이 부분은 주의해서 호출해줘야 한다.

 

 

전체코드 보기

더보기
import UIKit
import NMapsMap
 
class CenterMarkerViewController: UIViewController {
    
    let centerMarker = NMFMarker()
    
    override func viewDidLoad() {
        super.viewDidLoad()
 
        let naverMap = NMFNaverMapView(frame: view.frame)
        view.addSubview(naverMap)
        
        let cameraUpdate = NMFCameraUpdate(scrollTo: NMGLatLng(lat: 37.479132, lng: 127.011770))
        naverMap.mapView.moveCamera(cameraUpdate)
        
        centerMarker.position = naverMap.mapView.cameraPosition.target
        centerMarker.captionText = "마커"
        centerMarker.captionAligns = [NMFAlignType.top]
        centerMarker.mapView = naverMap.mapView
        
        naverMap.mapView.addCameraDelegate(delegate: self)
    }
 
    func callRequest(coords: String, completion: @escaping (Data) -> Void) {
        
        guard var urlComponents = URLComponents(string: "https://naveropenapi.apigw.ntruss.com/map-reversegeocode/v2/gc") else {
            print("URL Components Error")
            return
        }
        
        let queryItemArray = [
            URLQueryItem(name: "coords", value: coords),
            URLQueryItem(name: "orders", value: "roadaddr"),
            URLQueryItem(name: "output", value: "json")
        ]
        urlComponents.queryItems = queryItemArray
        
        guard let url = urlComponents.url else {
            print("URL Error")
            return
        }
        
        var urlRequest = URLRequest(url: url)
        
        urlRequest.addValue("클라이언트 Id", forHTTPHeaderField: "X-NCP-APIGW-API-KEY-ID")
        urlRequest.addValue("Secret 키", forHTTPHeaderField: "X-NCP-APIGW-API-KEY")
        
        URLSession.shared.dataTask(with: urlRequest) { data, response, error in
            
            guard let data = data, let response = response as? HTTPURLResponse, response.statusCode == 200 else {
                print("status code error")
                return
            }
            completion(data)
        }.resume()
    }
    
}
 
extension CenterMarkerViewController: NMFMapViewCameraDelegate {
    
    func mapView(_ mapView: NMFMapView, cameraIsChangingByReason reason: Int) {
        centerMarker.position = mapView.cameraPosition.target
    }
    
    func mapView(_ mapView: NMFMapView, cameraDidChangeByReason reason: Int, animated: Bool) {
        centerMarker.position = mapView.cameraPosition.target
    }
    
    func mapViewCameraIdle(_ mapView: NMFMapView) {
        print(mapView.cameraPosition.target)
        let coords = "\(mapView.cameraPosition.target.lng)" + "," + "\(mapView.cameraPosition.target.lat)"
        callRequest(coords: coords) { data in
            do {
                let decodeData = try JSONDecoder().decode(ReverseGeocodingModel.self, from: data)
                print(decodeData)
            } catch {
                print(error)
            }
        }
    }
    
}
 
struct ReverseGeocodingModel: Decodable {
    let results: [Address]
}
 
struct Address: Decodable {
    let region: Region
    let land: Land
}
 
struct Region: Decodable {
    let area1: Area1
    let area2, area3, area4: Area
}
 
struct Area1: Decodable {
    let name, alias: String
}
 
struct Area: Decodable {
    let name: String
}
 
struct Land: Decodable {
    let number1, number2: String
    let addition0: Addition0
    let name: String?
}
 
struct Addition0: Decodable {
    let type, value: String
}

 

etc-image-1

지도를 드래그해서 중앙마커를 움직이고, 움직임을 멈출 때마다

etc-image-2

ReverseGeocodingModel 형태로 데이터가 받아지는 것을 알 수 있다.

마지막 데이터를 보자면 '서울특별시 서초구 서초동 219 대법원 청사'로 주소값을 얻을 수 있다.

(데이터가 많기 때문에 본인이 필요한 데이터에 맞는 모델을 만들어야 한다.)

도큐먼트 보러 가기