Google Maps Data APIの謎

SmartTraining 2.0.0のこと。

今回のバージョンアップは初期バージョンリリース以来ずっと実現したかったGoogle Mapsへのデータアップ機能実装がメイン。嬉しかったので自分へのご褒美という意味でメジャーバージョンをあげちゃいましたwバージョン管理なんてそんなもん。

Google Mapsへのデータアップロード、意外に手間取りました。つい最近までずっとMaps関連のAPIはGoogle Maps APIだけだと思っていて、外部アプリからデータのアップロードなんてできないものだと思い込んでいました。
ところが先週、何気に情報探していたらGoogle Maps Data APIなるものを発見。紛らわしー名前だなー。これ発見した時「勝てる!」思いましたよ。楽勝だってね。でもそこからが大変。

まずは、Androidアプリを開発してるわけなので、Javaライブラリを探す。発見。おおぉー、サンプルも載ってるし楽勝じゃねーか。と思いつつライブラリダウンロード。ダウンロードしたライブラリには普通のJava環境用のライブラリとandroid用のライブラリが格納されている。普通のJava環境用のものはjacksonやらkxmlやらcommons-loggingやらいろいろと依存ライブラリがあるらしく、Android上でそのまま動かすのはちょっとつらい。

そこでAndroid用ライブラリをためす。早速ライブラリを組み込み実験用アプリを作りマップのリストを取得してみる。が、なんかおかしい。Webサイト上のサンプルをそのまま書いてみてもEclipse上では赤い下線が出る。
「そんなメソッドねぇ」だと。そこでダウンロードしたJavaDocs見ると、Webサイト上のサンプルで使ってるメソッドなんてありません。Maps関連のクラスを見てもAUTH_TOKEN_TYPEとVERSIONのstaticなフィールドが定義されているだけ。んー、なんだこりゃ。ライブラリのソースをダウンロードしてみるがJavaDocs通りフィールドが2個定義されているだけ。。。オイw未実装かよwさんざん探したけどサンプルもありません。

一晩寝てわかりました。Webサイト上のサンプルはv1.4用のっぽいんですね。この当時のライブラリにAndroid版はない。ダウンロードしたのはv2.1。どおりでダメなわけです。

そんなわけでJavaライブラリは諦め(いや、諦めるまで相当後ろ髪ひかれる思いだったよ)。泣く泣くHTTP Reference見つつHTTPレベルから実装することに。めんどくせーなー。
まぁ、でもGoogle Docsへデータアップロードとか実装しているし、だいたいおんなじだよねー、と思いつつマイマップのリストを取得してみる。楽勝。ClientLoginで認証してからMapsサービスにFeedsする流れはだいたい一緒。次、データのアップロード。アップロードできるのはXML、CSV、KMLの3種類。マップなんだもんKMLだよね。ってことで試してみるが、HTTP 403だか500だかエラーが返ってくる。サンプル貼り付けてるだけなのに。同様にCSV送ってみても動作が変わらない。そこでXML送ってみる。HTTP 201 CREATEDが返ってきた。お!確かにマイマップにマップが作成されている。ただし、リファレンス上のサンプルだと、ラインデータをアップする方法が載っていない。んー、XMLで空マップ作った後で、KMLでラインデータ送るのかと思い試してみるがダメ。詰まりました。

そこで考えました。「My Maps Editor」がやってるんだからおんなじことやりゃいいんでしょ。幸いMaps Data APIのプロトコルはHTTPなのでパケットさえキャプチャすればだいたい実装できるはず。Androidでパケットキャプチャする一般的な方法はtcpdumpを利用します。探してみるとstrazzere.comとかに方法が載ってます。でも、このサイトAndroidのハッキング情報載せているサイトらしく、読んでるといろいろやってます。ネタ的には楽しい情報結構載ってます。MarketアプリのSSL認証を無効にするとか何とか、、、何やってんですかこの人。それは置いといて。

HT-03Aだとセキュリティ上の理由でtcpdumpできないため、方法はエミュレータかDevphone。エミュレータは、Googleアカウント情報を登録できないため、My Maps Editorが動きません。(というか、エミュレータ上でMy Maps Editor動かすためには、一旦Devphone上にインストールしてapkファイルをpullしてエミュレータにインストール。。。めんどくさ)そこで、Devphone上で動作するMy Maps Editorの通信キャプチャするためtcpdump実行。そのまま実行すると「Permissionがない」、だと。うーん、悩みました。いろいろ探して見つけたサイトでヒント見つかりました。結局ごちゃごちゃやっていたので正確な情報ではないかもしれませんが、ポイントは以下。
・adb rootしとくことであとでadb shell実行時にtcpdumpがルート権限で動かせる?
・tcpdumpはエミュレータの/system/xbin/tcpdumpが入っているのでadb pullで引っこ抜いてきてDevphone上の同一パスにpushする。
まとめると、
> adb root
> adb remount
> adb push エミュレータから引っこ抜いてきたtcpdump /system/xbin/tcpdump
> adb shell chmod 6755 /system/xbin/tcpdump
> adb shell tcpdump -vv -s 0 -w /sdcard/output.cap
> adb pull . /sdcard/output.cap
取れたoutput.capをwiresharkで開く。
以上。

ついに見れましたパケット。ハイ、HTTP Referenceに載ってないことやってますよー。いや、だいたい同じようなことをやってるんだけどね。
ざっくりとした概要。
①ClientLogin
②「Uploading XML」の内容のXMLをhttp://maps.google.com/maps/feeds/maps/default/fullにPOST。
 レスポンスに含まれるuserIdとmapId(と思われるもの)を確保。
③②と同様のXMLをhttp://maps.google.com/maps/feeds/maps/userID/full/mapIDにPOST。(②で確保したuserIDとmapIDに置き換え)
④「Uploading KML」の内容のKMLをhttp://maps.google.com/maps/feeds/features/userID.mapsID/fullにPOST。(②で確保したuserIDとmapIDに置き換え)
⑤④と同様のKMLを③で使ったURLに送信。
⑥③とまったく同じ内容のリクエストを送信(個人的には〆の③と呼ぶ。これを送らないとマップデータが中途半端な状態で表示される)。

②と⑤のリクエストに設定するヘッダ
Accept-Encoding:gzip
Content-Type:application/atom+xml
Authorization:GoogleLogin auth=①で取得したToken
Connection:Keep-Alive
Content-Encoding:gzip
User-Agent:Android-GData/1.1 (dream DRC83); gzip

③と⑥には上記ヘッダに加え、
X-HTTP-Method-Override:PUT
を付加。
ヘッダ見てわかるようにBody部がgzipされてます。たぶんしなくてもいいと思います。

そもそも、なんで同じボディを二回ずつ別のURLに送って、〆の③をせねばならんのか。途中で通信エラーが起きたら途中までPOSTしたデータが中途半端に残るので、userId&mapIDを指定してマップデータをDELETEしてロールバックしてやるのが望ましいと思います。実際SmartTrainingでもロールバック実装しています。Delete周りはドキュメントどおりでした。
この方法、パケット見て真似してるだけなので他にも方法があるのかもしれません。もしくは、私のドキュメントの読みが足らず、実はちゃんとやるべきことが書いてあるのかもしれません。

というわけで、この内容と同様なことをやろうとしている方、業務でやろうとしているのだったら辞めた方がいいです。6回もリクエスト投げるんだから遅いし。「Maps Data APIがドキュメントと違う動作なので実装不可です」と発注者に教えてあげた方がいいです。そのうち、Android向けライブラリがちゃんと実装されるのを待った方がいいと思うな。

コメント

このブログの人気の投稿

Execノードを使う

Joinノードを使う(その4)

SendGridのX-SMTPAPIヘッダの使い方(Section Tags、Substitution Tags編)