R) 전처리 - 지오코딩(Google Map)

R) 전처리 - 지오코딩(Google Map)

지도상에 특정 지점을 표기하기 위해서는 위경도 좌표가 필요하다. 이를 위해서는 한글 또는 영문 주소를 위경도 좌표로 변환해야 하는데 Google Map API를 활용하여 변환하는 방법을 알아보자.

개요

지도 위에 특정 지점을 표기하기 위해서 위도와 경도 좌표가 필요하다. 적당히 표기할 수 있기도 하겠지만 지점 개수가 많거나 좀 더 정확하게 하려면 정확한 위경도 좌표 확보가 필수이다. 그런데 보통 특정 지점의 위경도 좌표 정보가 있기 보다는 한글 또는 영문 주소가 있는데 이를 위경도 좌표로 변환하는 것을 지오코딩(geocoding)이라고 한다. 참고로 지오코딩의 반대 개념이 역지오코딩(reverse geocoding)이다. 아무튼 이 지오코딩을 하기 위해 여기서는 Google 서비스를 활용하고자 한다.

Google Map API는 GCP(Google Cloud Platform) 서비스 중 하나이다. 해당 서비스를 이용하기 위한 API 키 발급은 GCP - 지오코딩(geocoding)에서 다루고 있으니 해당 포스팅을 참고하여 API 키를 발급받은 후 본 포스팅의 코드를 실습하도록 한다.

준비

GCP에서 생성한 Google Map 관련 프로젝트로 가서 API 키를 가져와 별도의 객체에 다음과 같이 등록한다. (아래 API 키는 존재하지 않는 키 이다.)

1
mykey = "AIzaSyBpkJubAqtwhJx0YRpDXEVR8Y9puaE6zf8"

만약 API 키를 잘못 입력하거나 만료된 것을 사용한다면, 다음과 같이 The provided API key is expired. 라는 에러 문구를 볼 수 있을 것이다.

1
2
3
4
gc = geocode(enc2utf8("서울특별시 시청"))
## Source : https://maps.googleapis.com/maps/api/geocode/json?address=%EC%84%9C%EC%9A%B8%ED%8A%B9%EB%B3%84%EC%8B%9C+%EC%8B%9C%EC%B2%AD&key=xxx
## 경고: Geocoding "서울특별시 시청" failed with error:
## The provided API key is expired.

ggmap 패키지

R에는 Google Map API를 손쉽게 이용할 수 있도록 도와주는 ggmap 패키지가 있다. 패키지의 각종 함수를 사용하기 위해서는 register_google() 함수에 발급받은 API 키를 입력하여 실행하도록 하자.

1
2
library("ggmap")
register_google(key = mykey)

호출

테스트 환경이 Windows 운영체제이기 때문에 인코딩(encoding)을 UTF-8로 바꿔주는 함수인 enc2utf8() 함수를 사용하였으며, MacOS나 Linux/UNIX 계열 운영체제는 enc2utf8() 함수를 사용하지 않아도 정상동작 할 것이다. 그리고 출력결과는 다음과 같이 tibble임을 알 수 있다.

1
2
3
4
5
6
geocode(enc2utf8("서울특별시 시청"))
## Source : https://maps.googleapis.com/maps/api/geocode/json?address=%EC%84%9C%EC%9A%B8%ED%8A%B9%EB%B3%84%EC%8B%9C+%EC%8B%9C%EC%B2%AD&key=xxx
## # A tibble: 1 x 2
## lon lat
## <dbl> <dbl>
## 1 127. 37.6

geocode() 함수에는 “output” 인자에 latlon/latlona/more/all 을 할당할 수 있는데 기본값이 “latlon”이고 다른 값을 할당하면 그에 맞는 결과가 나오며 결과 예시는 다음과 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
geocode(enc2utf8("서울특별시 시청"), output = "latlona")
## Source : https://maps.googleapis.com/maps/api/geocode/json?address=%EC%84%9C%EC%9A%B8%ED%8A%B9%EB%B3%84%EC%8B%9C+%EC%8B%9C%EC%B2%AD&key=xxx
## # A tibble: 1 x 3
## lon lat address
## <dbl> <dbl> <chr>
## 1 127. 37.6 110 sejong-daero, myeong-dong, jung-gu, seoul, south korea

geocode(enc2utf8("서울특별시 시청"), output = "more")
## Source : https://maps.googleapis.com/maps/api/geocode/json?address=%EC%84%9C%EC%9A%B8%ED%8A%B9%EB%B3%84%EC%8B%9C+%EC%8B%9C%EC%B2%AD&key=xxx
## # A tibble: 1 x 9
## lon lat type loctype address north south east west
## <dbl> <dbl> <chr> <chr> <chr> <dbl> <dbl> <dbl> <dbl>
## 1 127. 37.6 city_ha~ rooftop 110 sejong-daero, myeong-dong, jung-~ 37.6 37.6 127. 127.

“all”을 “output”에 할당하는 경우 굉장히 많은 정보가 나오는데 데이터프레임 형태가 아닌 리스트로 결과가 출력되기 때문에 별도의 정제가 필요하다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
gc_result = geocode(enc2utf8("서울특별시 시청"), output = "all")
## Source : https://maps.googleapis.com/maps/api/geocode/json?address=%EC%84%9C%EC%9A%B8%ED%8A%B9%EB%B3%84%EC%8B%9C+%EC%8B%9C%EC%B2%AD&key=xxx
str(gc_result)
## List of 2
## $ results:List of 1
## ..$ :List of 5
## .. ..$ address_components:List of 7
## .. .. ..$ :List of 3
## .. .. .. ..$ long_name : chr "110"
## .. .. .. ..$ short_name: chr "110"
## .. .. .. ..$ types :List of 1
## .. .. .. .. ..$ : chr "premise"
## .. .. ..$ :List of 3
## .. .. .. ..$ long_name : chr "Sejong-daero"
## .. .. .. ..$ short_name: chr "Sejong-daero"
## .. .. .. ..$ types :List of 3
## .. .. .. .. ..$ : chr "political"
## .. .. .. .. ..$ : chr "sublocality"
## .. .. .. .. ..$ : chr "sublocality_level_4"
## .. .. ..$ :List of 3
## .. .. .. ..$ long_name : chr "Myeong-dong"
## .. .. .. ..$ short_name: chr "Myeong-dong"
## .. .. .. ..$ types :List of 3
## .. .. .. .. ..$ : chr "political"
## .. .. .. .. ..$ : chr "sublocality"
## .. .. .. .. ..$ : chr "sublocality_level_2"
## .. .. ..$ :List of 3
## .. .. .. ..$ long_name : chr "Jung-gu"
## .. .. .. ..$ short_name: chr "Jung-gu"
## .. .. .. ..$ types :List of 3
## .. .. .. .. ..$ : chr "political"
## .. .. .. .. ..$ : chr "sublocality"
## .. .. .. .. ..$ : chr "sublocality_level_1"
## .. .. ..$ :List of 3
## .. .. .. ..$ long_name : chr "Seoul"
## .. .. .. ..$ short_name: chr "Seoul"
## .. .. .. ..$ types :List of 2
## .. .. .. .. ..$ : chr "administrative_area_level_1"
## .. .. .. .. ..$ : chr "political"
## .. .. ..$ :List of 3
## .. .. .. ..$ long_name : chr "South Korea"
## .. .. .. ..$ short_name: chr "KR"
## .. .. .. ..$ types :List of 2
## .. .. .. .. ..$ : chr "country"
## .. .. .. .. ..$ : chr "political"
## .. .. ..$ :List of 3
## .. .. .. ..$ long_name : chr "100-101"
## .. .. .. ..$ short_name: chr "100-101"
## .. .. .. ..$ types :List of 1
## .. .. .. .. ..$ : chr "postal_code"
## .. ..$ formatted_address : chr "110 Sejong-daero, Myeong-dong, Jung-gu, Seoul, South Korea"
## .. ..$ geometry :List of 3
## .. .. ..$ location :List of 2
## .. .. .. ..$ lat: num 37.6
## .. .. .. ..$ lng: num 127
## .. .. ..$ location_type: chr "ROOFTOP"
## .. .. ..$ viewport :List of 2
## .. .. .. ..$ northeast:List of 2
## .. .. .. .. ..$ lat: num 37.6
## .. .. .. .. ..$ lng: num 127
## .. .. .. ..$ southwest:List of 2
## .. .. .. .. ..$ lat: num 37.6
## .. .. .. .. ..$ lng: num 127
## .. ..$ place_id : chr "ChIJKwjLMvOifDURqPAMQqxwK-k"
## .. ..$ types :List of 4
## .. .. ..$ : chr "city_hall"
## .. .. ..$ : chr "establishment"
## .. .. ..$ : chr "local_government_office"
## .. .. ..$ : chr "point_of_interest"
## $ status : chr "OK"

ggmap 패키지는 사용하기 편리하지만 주소가 한글이 아니라 영문으로 나오기 때문에 좀 아쉬운 부분이 있다. 그렇다면 URL을 직접 호출하여 결과를 받아보도록 하자.

기타 함수

각종 계정 정보 확인하는 함수가 다양한데 그 중 몇 개를 소개하자면 다음과 같다.

1
2
3
4
5
6
7
8
google_account() # 계정 종류
## [1] "standard"

google_second_limit() # 초당 호출 제한
## [1] 50

google_day_limit() # 일 호출 제한
## [1] Inf

기본 함수 활용

모든 설정을 자유자제로 설정하려면 직접 호출 URL을 조립하는 것이 좋다. API 관련 공식 문서는 다음의 링크에서 확인할 수 있다.
https://developers.google.com/maps/documentation/geocoding/overview?hl=ko

API 호출 기본 형식은 다음과 같다.

https://maps.googleapis.com/maps/api/geocode/outputFormat?parameters

주요 API 호출 파라미터는 다음과 같다.
outputFormat: 출력 형태. json 또는 xml 중 하나 선택 가능
address: 지오코딩 대상 주소
key: API key
language: 출력 언어

참고로 지원하는 언어는 공식 API 문서 페이지에서 확인할 수 있으며 다음과 같은 내용을 볼 수 있다.
Geocoding API 지원 언어

다음은 호출하기 위한 URL을 조립하는 코드이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
addr = "2호선 강남역"

url_main = "https://maps.googleapis.com/maps/api/geocode/"
url_par_output = "xml"
url_f = paste0(url_main, url_par_output, "?")

url_par_addr = enc2utf8(addr)
url_par_key = mykey
url_par_lang = "ko"
url_par = paste(paste0("address=", url_par_addr),
paste0("key=", url_par_key),
paste0("language=", url_par_lang),
sep = "&")

url = paste0(url_f, url_par)
url = URLencode(url) # 필수
url
## [1] "https://maps.googleapis.com/maps/api/geocode/xml?address=2%ED%98%B8%EC%84%A0%20%EA%B0%95%EB%82%A8%EC%97%AD&key=AIzaSyBpkJubAqtwhJx0YRpDXEVR8Y9puaE6zf8&language=ko"

조합한 URL을 readLines() 사용하여 호출하는 예제는 다음과 같다. 단, 인코딩이 UTF-8이기 때문에 Windows 운영체제를 사용하는 경우 다음의 코드와 같이 입력해야 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
readLines(url, encoding = "UTF-8")
## [1] "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
## [2] "<GeocodeResponse>"
## [3] " <status>OK</status>"
## [4] " <result>"
## [5] " <type>establishment</type>"
## [6] " <type>point_of_interest</type>"
## [7] " <type>transit_station</type>"
## [8] " <formatted_address>대한민국 서울특별시 역삼1동 지하철2호선강남역</formatted_address>"
## [9] " <address_component>"
## [10] " <long_name>지하철2호선강남역</long_name>"
## [11] " <short_name>지하철2호선강남역</short_name>"
## [12] " <type>establishment</type>"
## [13] " <type>point_of_interest</type>"
## [14] " <type>transit_station</type>"
## [15] " </address_component>"
## [16] " <address_component>"
## [17] " <long_name>역삼1동</long_name>"
## [18] " <short_name>역삼1동</short_name>"
## [19] " <type>political</type>"
## [20] " <type>sublocality</type>"
## [21] " <type>sublocality_level_2</type>"
## [22] " </address_component>"
## [23] " <address_component>"
## [24] " <long_name>서울특별시</long_name>"
## [25] " <short_name>서울특별시</short_name>"
## [26] " <type>administrative_area_level_1</type>"
## [27] " <type>political</type>"
## [28] " </address_component>"
## [29] " <address_component>"
## [30] " <long_name>대한민국</long_name>"
## [31] " <short_name>KR</short_name>"
## [32] " <type>country</type>"
## [33] " <type>political</type>"
## [34] " </address_component>"
## [35] " <address_component>"
## [36] " <long_name>135-080</long_name>"
## [37] " <short_name>135-080</short_name>"
## [38] " <type>postal_code</type>"
## [39] " </address_component>"
## [40] " <geometry>"
## [41] " <location>"
## [42] " <lat>37.5008580</lat>"
## [43] " <lng>127.0262300</lng>"
## [44] " </location>"
## [45] " <location_type>GEOMETRIC_CENTER</location_type>"
## [46] " <viewport>"
## [47] " <southwest>"
## [48] " <lat>37.4995090</lat>"
## [49] " <lng>127.0248810</lng>"
## [50] " </southwest>"
## [51] " <northeast>"
## [52] " <lat>37.5022070</lat>"
## [53] " <lng>127.0275790</lng>"
## [54] " </northeast>"
## [55] " </viewport>"
## [56] " </geometry>"
## [57] " <place_id>ChIJkzOF-VihfDURq_VGq8IMQ4I</place_id>"
## [58] " </result>"
## [59] "</GeocodeResponse>"

상기 예제는 readLines() 함수를 사용했지만 실제로 호출 결과를 잘 다루기 위해서는 API 호출 결과 설정에 맞는 패키지 사용이 필요하다. 예를 들어 JSON인 경우 jsonlite 같은 JSON 형식을 다루는 패키지를, XML 형식으로 설정한 경우 rvest 패키지 또는 이와 유사한 패키지를 활용하여 XML 형식의 출력을 다루는 것이 좋다.

참고

보다 정확한 위경도 좌표를 위해서는 최대한 자세한 주소를 입력하는 것이 좋다. 예를 들어 “교대역” 이라고 할 경우 서울/대구/부산 중 어디 교대역을 지칭하는지 Google Map은 알길이 없다. 그래서 “교대역” 보다는 “서울 교대역” 이나 “서울 2호선 교대역” 이렇게 입력하는 것이 좋고, 가장 정확한 주소를 얻으려 한다면 “서울 서초구 서초대로 지하 294” 로 완전한 주소를 입력하는 것이 좋다.

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×