[iOS] NaverMap API - Geocoding

주의점!

 

NAVER CLOUD PLATFORM

cloud computing services for corporations, IaaS, PaaS, SaaS, with Global region and Security Technology Certification

www.ncloud.com

 

 

네이버 클라우드 페이지에는 개인용 / 공공기관용 / 금융 클라우드 용으로 나눠져 있다.

우리는 분명 개인용으로 키 값을 부여받았기에 꼭! 개인용으로 접속을 해야 한다.

이렇게 보여야 한다...

 

홈페이지에서 왼쪽 상단을 보면 위와 같이 나타나져 있으면 개인용이다.

이를 구별해야 하는 이유는 후에 사용가이드(도큐먼트)를 참고할 때,

공공기관용이나 금융 클라우드 용으로 들어가면 API를 요청하는 url이 개인용과 다르기 때문에 응답을 받을 수 없으며,

나도 이 이슈 때문에 몇 시간 동안 헤맸다. ㅜㅜ

 

 

 

네이버 클라우드가 제공하는 API

 

네이버 클라우드에서 Map과 관련해서 제공하는 API는 위의 5개가 있다.

  • Static Map: 요청한 지도 이미지를 받을 수 있다.
  • Directions: 경로 데이터를 조회할 수 있고 옵션에 경유지를 최대 5개 혹은 15개까지 추가 가능하다.
  • Geocoding: 지번주소, 도로명 주소를 지도 좌표로 변환할 수 있다.
  • Reverse Geocoding: 지도 좌표를 지번주소, 도로명 주소, 법정동, 행정동 정보로 변환할 수 있다.

 

이 중 Static Map을 제외하고 API를 활용해 보자.

 

위 API를 사용하려면 Client ID와 Client Secret 키 둘 다 필요하니 꼭 복사해 두자.

 

 

 

Geocoding이란?

주소를 좌표로 변환하는 기술을 뜻한다. 예를 들어 서울역의 주소를 입력하면 네이버맵 기준의 좌표로 이를 반환해준다.

 

 

 

 

Geocoding API

Geocoding의 HTTP 메서드는 'get'으로 요청 바디가 필요하지 않다. 

 

하지만 아까 위에서 복사해 두라고 한 ID와 Secret key를 헤더로 보내야 응답을 받을 수 있다.

 

 

 

 

 

코드

우선 위의 정보를 바탕으로 네트워크 통신 코드를 작성해 보자.

    func callRequest(query: String, completion: @escaping (Data) -> Void) {
        
        guard var urlComponents = URLComponents(string: "https://naveropenapi.apigw.ntruss.com/map-geocode/v2/geocode") else {
            print("URL Components Error")
            return
        }
        
        let queryItemArray = [
            URLQueryItem(name: "query", value: query)
        ]
        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("Seceret 키", 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()
    }

 

간단하게 설명해 보자면 URLSession으로 네트워크 통신을 하며,

메서드는 default 값인 get으로 설정된다.

그리고 쿼리 값을 줘야 하기 때문에 queryItemArray를 통해 주소값을 넣어줬으며 

마지막으로 Header에 포함되어야 하는 키값(클라이언트id, 시크릿키)들을 addValue로 추가해 줬다.

 

이제 이 메서드를 텍스트 필드에 주소를 입력하고 검색 버튼을 누르면 실행되게 만들면 된다.

 

 

전체 코드 보기

더보기
import UIKit
import NMapsMap

class GeocodingViewController: UIViewController {
    
    let textField = UITextField()
    let label = UILabel()
    
    // view 객체 추가
    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .white
        
        textField.placeholder = "주소를 입력해주세요"
        textField.borderStyle = .roundedRect
        textField.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(textField)
        
        let searchButton = UIButton(type: .system)
        searchButton.setTitle("검색", for: .normal)
        searchButton.translatesAutoresizingMaskIntoConstraints = false
        searchButton.addTarget(self, action: #selector(searchButtonTapped), for: .touchUpInside)
        view.addSubview(searchButton)
        
        label.text = "This is a label"
        label.numberOfLines = 0
        label.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(label)
        
        NSLayoutConstraint.activate([
            textField.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20),
            textField.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            
            searchButton.centerYAnchor.constraint(equalTo: textField.centerYAnchor),
            searchButton.leadingAnchor.constraint(equalTo: textField.trailingAnchor, constant: 10),
            searchButton.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
            
            textField.trailingAnchor.constraint(equalTo: searchButton.leadingAnchor, constant: -10),
            searchButton.widthAnchor.constraint(equalToConstant: 60),
            
            label.topAnchor.constraint(equalTo: textField.bottomAnchor, constant: 20),
            label.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            label.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20)
        ])
        
    }
    // 네트워크 통신 메서드
    func callRequest(query: String, completion: @escaping (Data) -> Void) {
        
        guard var urlComponents = URLComponents(string: "https://naveropenapi.apigw.ntruss.com/map-geocode/v2/geocode") else {
            print("URL Components Error")
            return
        }
        
        let queryItemArray = [
            URLQueryItem(name: "query", value: query)
        ]
        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()
    }
    
    @objc func searchButtonTapped() {
        guard let query = textField.text, !query.isEmpty else {
            print("Query is empty")
            return
        }
        
        self.callRequest(query: query) { data in
            do {
                let decodedData = try JSONDecoder().decode(Geocoding.self, from: data)
                print("Received Data: \(decodedData)")
                let lastData = decodedData.addresses.first
                DispatchQueue.main.async {
                    self.label.text = (lastData?.roadAddress ?? "데이터X") + (lastData?.x ?? "0") + "," + (lastData?.y ?? "0")
                }
            } catch {
                print(error)
            }
        }
        
    }
    
}

// 모델
struct Geocoding: Codable {
    let status: String
    let meta: Meta
    let addresses: [Address]
    let errorMessage: String
}

struct Address: Codable {
    let roadAddress, jibunAddress, englishAddress: String
    let addressElements: [AddressElement]
    let x, y: String
    let distance: Double
}

struct AddressElement: Codable {
    let types: [String]
    let longName, shortName, code: String
}

struct Meta: Codable {
    let totalCount, page, count: Int
}

 

 

 

 

결과적으로 주소를 통해 좌표 값을 가져올 수 있게 되었다.