ホテル街を見つける(2)RとGoogleAPIでジオコーディング
前回に続いて、ホテル街を見つける技術編第二弾。前回は住所一覧のベクトルをcsvファイルにしまうところまでやりました。
今回は住所一覧を元に緯度経度をゲットする ジオコーディング のやり方について。方法としてはRからGoogle APIを利用します。
流れ
今回の最終目標は以下の?を埋めたcsvファイルを吐き出すところまで。
住所 | 緯度 | 経度 |
---|---|---|
北海道なんたら1 | ? | ? |
北海道なんたら2 | ? | ? |
北海道なんたら3 | ? | ? |
… | ? | ? |
参照
ジオコーディングをやってみる
ジオコーディングとは、住所を緯度・経度の座標値に変換すること。住所のままだとプロットの都合上塩梅が悪いので、緯度経度のセットに変換したい。例えば、東京駅の住所がわかっていたとして、わからない緯度と経度をひっぱってきたい。
住所 | 緯度 | 経度 |
---|---|---|
東京都千代田区丸の内一丁目 | ? | ? |
何はなくともジオコーディングしてみよう(ここは先の参照サイト)のコードをほぼそのままお借りしてます)。
# ライブラリ読み込み library(rjson) library(RCurl) # 住所 hoge <- "東京都千代田区丸の内一丁目" # 欲しい住所を指定したURLを作成 url <- paste0("http://maps.googleapis.com/maps/api/geocode/json?address=", hoge, "&sensor=false") # URLを適切にエンコード url_utf8 <- URLencode(iconv(url, "", "UTF-8")) # URLからJSONデータを取得し、リスト形式に変換 getJSON <- fromJSON(getURL(url_utf8)) # リストの中から緯度と経度を引っ張りだす getJSON$results[[1]]$geometry$location
結果として以下の様に緯度と経度を獲得することができる。
住所 | 緯度 | 経度 |
---|---|---|
東京都千代田区丸の内一丁目 | 35.68332 | 139.7674 |
paste0()
は引数同士の間に何も挟まず、文字列を結合する(参照)。incov()
は第1引数に与えた文字列を、第2引数のコードから第3引数のコードへ変換する(参照)。ここではUTF-8へと変換している。さらにURLencode()
でURLエンコードへ変更する。よくわからないが、URLに入っていてはまずい文字を変換するっぽい(参照)。
ホテルの緯度・経度を獲得する
前回の記事で以下のように住所一覧を得て address.csv にしまった。今回はこの緯度と経度の?をすべて埋めたcsvファイルを作ることが目標である。
住所 | 緯度 | 経度 |
---|---|---|
北海道なんたら1 | ? | ? |
北海道なんたら2 | ? | ? |
北海道なんたら3 | ? | ? |
… | ? | ? |
やることは至って単純で、先ほどの「東京都千代田区丸の内一丁目」(= hoge)のところへ、順番に住所を投げ込んでいけば良い。乗っかかりっきりですが、Rで(逆)ジオコーディングさんのコードのマイナーチェンジで挑みます(感謝)。
# データの読み込み text <- read.csv("address.csv",header=T,stringsAsFactors = FALSE) text <- text$x # ライブラリ読み込み library(RCurl) library(rjson) library(RgoogleMaps) # 関数作成 GEO_new <- function(address, para = list(sensor = "false", language = "ja", region = "JP"), proxy = NULL) { curl_set <- getCurlHandle() if(!is.null(proxy)) curlSetOpt(.opts = list(proxy = proxy), curl = curl_set) parameters <- paste0("&", names(para), "=", unname(unlist(para)), collapse = "") GeoRequest <- iconv(paste0("http://maps.googleapis.com/maps/api/geocode/json?address=", address, parameters), "", "UTF-8") geodata <- vector("list", length(address)) for(i in seq_along(address)) { getJSON <- fromJSON(getURL(URLencode(GeoRequest[i]), curl = curl_set)) if (length(getJSON$results)!=0){ geodata[[i]] <- as.data.frame(getJSON$results[[1]]$geometry$location) Sys.sleep(0.5) print(paste(i,"=================="))} else { geodata[[i]] <- NA Sys.sleep(0.5) print(paste(i,"失敗した…"))}} cbind(address, do.call("rbind", geodata)) } # ジオコーディング:3回に分けて3つのセットにしまう(ちなみにうまくいかないと思うので下記参照) location0001_2000 <- GEO_new(text[1:2000]) location2001_4000 <- GEO_new(text[2001:4000]) location4001_6088 <- GEO_new(text[4001:6088]) # 縦につないで一つのデータセットに location <- rbind(location0001_2000,location2001_4000,location4001_6088) # 書き出し write.csv(location, "location.csv", quote=FALSE, row.names=FALSE)
大きな流れは、データを読み込んで text ベクトルに住所を入れ込む。関数 GEO_new()
を定義する。2000ずつジオコーディングした住所をしまいこんで、縦にくっつける。csvで書き出す。
変更点は以下の3点。第1にエラーの処理した。第2に現在の進行状況の確認と住所の獲得に失敗したかどうかの表示した。第3にAPIのリクエスト間に時間を設けた。重要な注意点もあるので順に説明する。
① エラーを吐いても止めない
当初のコードだと途中で エラーを吐いた場合止まる 。該当住所がないのか何なのか、多くはないが住所の獲得に失敗しエラーを吐いてしまった。そこでfor文の中にif文をはさみ、エラーを吐いた場合は該当箇所にNAを入れこむ仕様にした。これで止まらない。中身としては先に述べたように、getJSONにリストが入る仕様になっているのだが、失敗するとgetJSON$results
にlist()
が入ってしまって[[1]]
にアクセスできずエラーが起きる。これを防いでいる。
② 何ケースやったか/どこで失敗したか
print()
を挟み込んで 何ケース進んだかを表示 するようにした。また、 失敗した時は「失敗した…」と表示 するようにした。そのため、どのケースがNAになっているのかとりあえずわかる。
③ APIリクエスト制限
リクエストを過剰に送るとgoogle側からstopが入りやはりエラーする 。具体的には、
IPあたり24時間に2500リクエスト、秒間5リクエスト
参照 意外に多いGoogle Maps関連機能制限をまとめた、Google Geocoding APIの制約に関するよくある誤解
そこで、まず2500件制限に対策すべく上記のように 2000件ずつのアクセス にしている。上記のコードを一度に回すとおそらくGEO_new(text[2001:4000])
が500辺り以降ですべてエラーになるためNAが入ってしまう。3日間に分けて獲得するか、他のIPアドレスを用いて獲得しよう。yahooのサービスは制限がないためそちらを使っても良い、みたいな話もある。
次に秒間リクエストだが、Sys.sleep(0.5)
で アクセスごとに0.5秒止める ことにした。理論上は0.2でもいいはずなのだが、時々失敗するので試行錯誤の末0.5とした。この辺りはちょっといじったほうがいいかもしれない。
まとめ
以上で以って以下の様な location.csv ファイルがディレクトリにできたはず。やったね!
address | lat | lng |
---|---|---|
北海道なんたら1 | xx.xxxxx | xx.xxxxx |
北海道なんたら2 | xx.xxxxx | xx.xxxxx |
北海道なんたら3 | NA | NA |
… | xx.xxxxx | xx.xxxxx |
次回はいよいよこのデータを使ってQGISで地図上へのプロット、および「GISデータを用いた分析」っぽい分析を少し行います。QGISのダウンロードはあちこちに良い解説があるのでそちらを参照してください。ではでは。