読者です 読者をやめる 読者になる 読者になる

文系プログラマによるTIPSブログ

文系プログラマ脳の私が開発現場で学んだ事やプログラミングのTIPSをまとめています。

各種ジオコーディングapiの罠と対処法

位置情報 ジオコーディング

f:id:treeapps:20131225173548p:plain
各種ジオコーディングサービスですが、主に利用制限周りに罠があります。
これをしっていないと色々利用規約に違反してしまうので、気をつける必要があります。
それぞれ内容を確認し、まずは制限を知り、対策を検討する必要があります。

記事の最後に、実戦向きのジオコーディング処理を載せているので、是非ご覧下さい。

各種ジオコーダ

google geocoding v2

Upgrading Your Geocoding API Application To v3   |   Google Maps APIs   |   Google Developers

Google Geocoding V2 API の使用は、1 日あたり 15,000 件までという位置情報リクエストのクエリ制限の対象となりますこの制限を適用することにより、Geocoding API の不正使用や悪用を防ぐことができます。この制限は今後、予告なく変更される場合があります。また、サービスの不正使用を防ぐため、リクエスト頻度についても制限を設けています。24 時間の使用制限を超えた場合や、サービスを不正使用した場合は、Geocoding API の利用が一時的に停止されます。制限を超える利用が繰り返された場合、Geocoding API へのアクセスがブロックされます。
注: Geocoding API は Google マップと組み合わせて使用する場合にのみ使用できます。地図に表示せずにジオコーディングの結果のみを利用することは禁止されています。許可されている使用方法について詳しくは、Maps API 利用規約のライセンス制限をご覧ください。

https://developers.google.com/maps/documentation/geocoding/?hl=ja#Limits

同一IPで1日に15000回まで可能。ジオコードした結果はgoogle mapとの併用でのみ利用可能
クレジット表示が必要ないのは、google mapにおもいっきり「Google」って書いてあるからでしょう。


良い子の皆はサポートが切れたver2を使っちゃダメだぞ

google geocoding v3

The Google Geocoding API   |   Google Maps Geocoding API   |   Google Developers

使用制限
Google Geocoding API の使用は、1 日あたり 2,500 件までという位置情報リクエストのクエリ制限の対象となります(Google Maps API Premier をご利用の場合は、1 日あたり 100,000 件までリクエストを実行できます)。この制限を適用することにより、Geocoding API の不正使用や悪用を防ぐことができます。この制限は今後、予告なく変更される場合があります。また、サービスの不正使用を防ぐため、リクエスト頻度についても制限を設けています。24 時間の使用制限を超えた場合や、サービスを不正使用した場合は、Geocoding API の利用が一時的に停止されます。制限を超える利用が繰り返された場合、Geocoding API へのアクセスがブロックされます。
注: Geocoding API は Google マップと組み合わせて使用する場合にのみ使用できます。地図に表示せずにジオコーディングの結果のみを利用することは禁止されています。許可されている使用方法について詳しくは、Maps API 利用規約のライセンス制限をご覧ください。

https://developers.google.com/maps/documentation/geocoding/v2/?hl=ja#Limits

同一IPで1日に2500回まで可能。ジオコードした結果はgoogle mapとの併用でのみ利用可能
Google Maps API Premier(有料)の場合は1日に10万回まで可能になります。
クレジット表示が必要ないのは、google mapにおもいっきり「Google」って書いてあるからでしょう。


クライアントサイドのジオコーディングはgoogle geocoder一択ですな。

Yahoo!ジオコーダAPI

YOLP(地図):Yahoo!ジオコーダAPI - Yahoo!デベロッパーネットワーク
なんとどこにも利用制限が書いてません
唯一クレジットを表示しなければいけないという制限は見つかりました。
Yahoo!デベロッパーネットワーク:クレジット表示 - Yahoo!デベロッパーネットワーク
一生懸命探しましたが、どこにもありません。なんだこれ・・・
yahooの日本語ドキュメントはクソなので、本家サイトをみてみます。
ありました!

Rate Limit
The Geocoding service is limited to 5,000 queries per IP address per day. See information on rate limiting.

http://developer.yahoo.com/maps/rest/V1/geocode.html

同一IPで1日に5000回、と。
とその前に、Geocoding APIに「このAPIは非推奨(いずれ削除される)で代わりにPlaceFinderを使え」と書いてます。
ではPlaceFinderを見てましょう。

RATE LIMITS
Use of the Yahoo! PlaceFinder API web service should not exceed 10,000 requests per day.
If you believe your application will exceed such volume, please contact us.

http://developer.yahoo.com/geo/placefinder/

1日に1万回、と。

しかし日本のジオコーダと本家のPlaceFinderは全く別物なので、利用制限も違うはず。
うーむ、日本のジオコーダの制限が解らん・・・


yahooのapiは全体的に利用制限が解りにくいんだよなあ・・・とりあえずYahoo!ジオコーダAPIは一日5万回アクセスが上限っぽく、実際私はそれくらい毎日実行しています。

ジオコーディングの制限

yahooはそもそも利用制限が不明なため解説不要とします。
google geocoding api v2はそろそろサポート切れなので同様に解説不要です。
google geocoding api v3について、制限の詳細を見ていきましょう。

ジオコーディングは時間とリソースを消費するタスクです。可能な場合は、既知の住所をあらかじめ(ここで説明する Geocoding API や他のジオコーディング サービスを使用して)ジオコーディングし、独自に設計した一時キャッシュにその結果を保存するようにしてください。

https://developers.google.com/maps/documentation/geocoding/?hl=ja#Audience

この説明に気になる一文があります。他のジオコーダも併用しろ、というのはまあいいとして、
独自に設計した一時キャッシュにその結果を保存するようにしてください
これです。曖昧な言葉ですが、つまり「サーバのDB等に保存しておいてキャッシュしろよ」、
ということでしょう。どのくらいキャッシュしていいかは解りません。。。
情報が更新されても困らないくらいの期間ならいいのだろうか。
緯度経度なんて情報の追加はあっても更新はほぼ無いでしょうから、永続キャッシュでいい気がします。


制限とか色々面倒なので、もういっそ住所と緯度経度が紐付いたデータが公式に存在しないか探してみます。


ありました。

・・・・・あるのかよ!!

国土交通省:位置参照情報ダウンロードサービス

位置参照情報ダウンロードサービス
あるんですねえ。しかも国土交通省のデータですよ。では早速利用規約を見てみます。
位置参照情報ダウンロードサービス
要約すると
国交省「金はとらねぇ。だだしこれ使って何が起きても知らん」
ということです。ここで配布されているデータは主に2種類です。
1,街区レベル

"都道府県名","市区町村名","大字・町丁目名","街区符号・地番","座標系番号","X座標","Y座標","緯度","経度","住居表示フラグ","代表フラグ","更新前履歴フラグ","更新後履歴フラグ"
"北海道","札幌市中央区","旭ケ丘一丁目","1","12","-105804.0","-75777.4","43.043819","141.319932","1","1","0","0"
"北海道","札幌市中央区","旭ケ丘一丁目","2","12","-105835.5","-75754.1","43.043538","141.320222","1","1","0","0"
"北海道","札幌市中央区","旭ケ丘一丁目","3","12","-105889.2","-75765.2","43.043054","141.320093","1","1","0","0"

2,大字・町丁目レベル

"都道府県コード","都道府県名","市区町村コード","市区町村名","大字町丁目コード","大字町丁目名","緯度","経度","原典資料コード","大字・字・丁目区分コード"
"01","北海道","01101","札幌市中央区","011010001001","旭ヶ丘一丁目","43.041403","141.319980","1","3"
"01","北海道","01101","札幌市中央区","011010001002","旭ヶ丘二丁目","43.039804","141.321595","1","3"
"01","北海道","01101","札幌市中央区","011010001003","旭ヶ丘三丁目","43.039789","141.319717","1","3"

なるほど、大字・町丁目レベルが一般的には使えそうですね。
私が個人的に大字・町丁目レベルレベルの全都道府県ファイルを調べてみた所、以下が解りました。
ファイル総行数:253898行(csv1行目のヘッダを含む)
ファイルサイズ:24MByte
ちょっとデータ数が少ない気がしますが、使えるでしょう。
これらを踏まえると、以下のように対処するといいでしょう。

  • 国交省のデータを事前にDBに保存しておき、基本はこれを使う。
  • 国交省のデータに住所が無い場合、google geocodingを使う。
  • ジオコーディングした結果はDBに保存していく。


いかにジオコーディングせずにキャッシュから取得するかが鍵だな!!ジオコーディングは目茶苦茶時間かかるから、キャッシュしまくろう!!

実践ジオコーディング!!

国交省のデータを使うとして、それでも緯度経度情報が不足する事があります。
1秒1リクエストなので、数万件ジオコーディングしようとすると1日で処理が終わらない事があります。
これらを極力解決するために、私が実際に業務で行っている方法を一例として書いてみます。

先に必要なものを列挙し、後述で詳細を書いていきます。

  • ジオコーディングキューテーブル
  • ジオコーディング結果テーブル
  • jsのgoogleジオコーディング
  • java等のサーバサイドのジオコーディングバッチ

※ テーブルはMySQLを使う前提で書いています。

猫でも解る簡易処理フロー

画像で簡単な処理フローを解説します。
おおまかにこんな感じです。
f:id:treeapps:20131225194011p:plain


画面表示時にサーバサイド側で緯度経度の存在有無をチェックして、有ったらそれを使う。無かったらgoogle geocoderでクライアントサイドでジオコーディングしつつ、取得した緯度経度を非同期でキャッシュDBに保存していく訳だな。

用意するもの:ジオコーディングキューテーブル

論理名 extra
住所 varchar(191) primary key
ステータス tinyint unsigned 0=未処理、1=処理済

同じ住所をキューに入れられないように住所をPKにします。
住所をtext型にしたいところですが、UTF-8 MB4の場合は最大191byteしかインデックスできません。
さすがにそんな長い住所は無視しても問題無いかと思います。(googleがそれをジオコードできると思えない)

住所をジオコーディングキューにinsertする前に、後述のジオコーディング結果テーブルを参照して、既にジオコーディングされていないかを確認し、未ジオコードの住所のみをキューに登録します。
これで無駄なジオコード処理を省けます。

わざわざキューテーブルを用意しているのは以下のメリットがあるからです。

  • 処理が失敗してもデータが残り続けるので、任意のタイミングでリトライできる。
  • 実装が楽。キューテーブルを全件ループして1件づつジオコードすればいい。
  • 手動で「100件だけ急いでジオコーディングしたい!」等の際にも使える。


住所は横棒の種類とか、表記揺れに注意しないと2重に住所ができたりするから注意だな

用意するもの:ジオコーディング結果テーブル

論理名 extra
住所 varchar(191) primary key
緯度 double
軽度 double

このテーブルに結果が蓄積されていきます。

用意するもの:クライアントサイドでGoogleジオコーディング

googlemapを表示する際に、ジオコーディング結果テーブルに緯度経度があったら、jsのgoogleジオコーディングを行わず、結果テーブルの値を使います。(jsのジオコーディングはしない)

ジオコーディング結果テーブルに緯度経度が無かったら、jsのgoogleジオコーディングを実行し、緯度軽度を取得します。

取得した緯度経度を使ってgooglemapを表示しつつ、ajax(非同期)でジオコーディング結果テーブルにジオコードで取得した緯度経度をinsertします。
非同期にすれば画面の描画を妨げる事もありません。
jsの場合は既に緯度経度があるので、キューテーブルをスキップして直接結果テーブルにinsertできます。

更に、googleジオコーディングは同一IPからは1日2500回の制限がありますが、jsでジオコーディングする場合は、そのIPはユーザのIPとなるため、1ユーザ2500回、つまりほぼ無限といえるでしょう。

後述するサーバサイドからジオコーディングの場合は、ジオコーディングするサーバのIPは大抵固定されているため、1日◯回、という制限にすぐひっかかってしまいます。

用意するもの:サーバサイドのジオコーディングバッチ

1日1回、サーバサイドからジオコーディングを行います。
googleだと1日2500回しか行えないので、Yahooジオコーダを使います。
公式の制限は不明ですが、1秒1リクエスト・1日5万回リクエストしても問題ないとネットに書いてあり、私は実際にそれくらい毎日リクエストしていますがBANされたりはしていません。

クライアントサイドからのジオコーディングも行うし、サーバサイドからのジオコーディングも行います。こうする事で日々緯度経度が蓄積されていき、ジオコーディングバッチの処理件数は徐々に減っていきます。

サーバサイドジオコーディングをする際は、キューテーブルの住所を元にYahooジオコーダで処理する訳ですが、一旦処理した住所はステータスを1=処理済み、に更新します。
ジオコーディングが全て完了したら、最後にキューテーブルの処理済データを一括物理削除します。

何故わざわざステータスを変えて最後に一括削除するような面倒な事をしているかというと、
1件ジオコーディングして1件キューを削除するとループ処理が面倒になるからです。
例えば1000件毎に処理行うような実装の場合、途中で1件削除したら次の1000件が1件ずれてしまいます。
キューにステータスを持たず、処理済住所をメモリに保持して後で一括deleteしてもいいですが、それだとOutOfMemoryになる危険性があります。
従って、ステータスカラムを使うのが最も楽でメモリ消費も少ないわけです。


やはりポイントはクライアントサイドとサーバサイドの両方でジオコーディングする事だな。そうしないとジオコーディング件数が多くて1日以上かかって翌日のジオコーディングバッチに干渉する危険があるんだよな。

おまけ:邪道な高速ジオコーディング

※ この方法は規約に違反する可能性があります

ほとんどのapiの制限として、1秒1リクエストという最大の鬼門がありますね。
そもそもジオコーディングが遅い最大の原因はこいつです。
もしこれが解消できたらどうでしょうか?
私が考えた邪道な解決策、それは・・・・



複数アカウント作成して複数非同期でジオコードする



という非常にアホな作戦です。
但し、これはyahooジオコーダを使い、更にサーバが複数台ある場合の話です。
同一IPでは制限にひっかかるので、例えば「develop環境」「staging環境」「product環境」の全サーバ分のアカウントを作成し、一斉に全環境でジオコードすれば、サーバの台数分高速化されます。
極端な話、サーバ10台で一斉にジオコードすれば10倍速くなりますね。

余りに単純でアホな作戦ですが、こんな解決策もありますね。(規約に違反しそうですが)


絶対マネしちゃだめだよ。これはあくまで理論上の話だよ。

総評

このように、ジオコーディングの有料サービスを使わずとも、無料版でかなり対応できると私は考えています。

私がよくやる方法としては、サイトをグローバルリリースする前に、前述のサーバサイドジオコーディングを回して、事前に大量に緯度経度を取得しておきます。するとリリース後のジオコーディング回数を劇的に減らす事ができます。但し、日々の運用で大まかに新住所が来る件数が解っている場合は問題ありませんが、大量の新住所が来るとジオコーディングが追いつかなくなるので、事前に教えて貰うか、ジオコーディングバッチの多重起動チェックをしてバッチの2重起動を防ぐとよいかと思います。


頑張って蓄積した緯度経度の座標データは、あらゆるサービスで有用に使え、流行り廃りの概念もほとんどない貴重なデータです。
皆さんも是非無料版でもめげずに頑張って下さい!!


日々蓄積していった緯度経度は社内全体で共有したりすると、自社サービスや運用中のサービスの機能拡張等、色々な可能性が広がって勝ち組になれるかも!?