Fusion Tables Client API for Java/Scala

Takami Torao Fusion Tables API v2 Drive API v3 Java 8 Scala 2.12
  • このエントリーをはてなブックマークに追加

概要

Java やその他の JavaVM 言語は Maven, Gradle, sbt などのビルドツールを利用して Maven リポジトリの Fusion Tables API のクライアントライブラリを利用するのが早いだろう。

sbt では以下の依存関係を build.sbt に記述すればよい。現時点の最新バージョンは v2 である。

libraryDependencies ++= Seq(
  "com.google.apis" % "google-api-services-fusiontables" % "v2-rev19-1.23.0"
)

Fusion Tables REST API は Google 認証を必要とする。それを行う OAuth2 は上記のライブラリで実装されているため Google API コンソール からアクセストークンや秘密鍵を入手するだけでよい。

Google 認証は:

  1. サードパーティの Web サービスから閲覧者が認証する。
  2. Android アプリからユーザが認証する。
  3. スタンドアロンのアプリケーションが認証する。

などいくつかパターンがありそれぞれ手段が異なるが、この記事ではスタンドアロンアプリケーションやバッチ処理が Fusion Tables を利用するケースを取り扱う。この実働コードは github で公開している。

アプリケーションの登録と認証

アプリケーションから Fusion Tables にアクセスするにはアクセスキーを入手する必要がある。

  1. Google API コンソールでプロジェクトを作成する。
  2. API ライブラリで Fusion Tables API を選択する。
  3. 「認証情報を作成」プロジェクトへの認証情報の追加、で「サービスアカウントキー」を作成する。

バッチ処理やスタンドアロンのアプリケーションは「サービスアカウント」として Google API を使用する。API コンソールで「サービスアカウントキー」を作成し、ダウンロードした秘密鍵の含まれる JSON ファイルを外部に公開されていないアプリケーションからアクセス可能な場所に保存する。

import scala.collection.JavaConverters._
import com.google.api.client.googleapis.auth.oauth2.GoogleCredential
import com.google.api.client.http.javanet.NetHttpTransport
import com.google.api.client.json.jackson2.JacksonFactory
import com.google.api.services.fusiontables.{Fusiontables, FusiontablesScopes}

object Main {
  def main(args:Array[String]):Unit = {

    // 認証情報の作成
    val in = new FileInputStream(args(0))
    val credential = GoogleCredential
      .fromStream(in)
    in.close()

    // FusionTables インスタンスの作成
    val fusiontables = new Fusiontables.Builder(
      new NetHttpTransport(), new JacksonFactory(),
      credential.createScoped(
        Seq(FusiontablesScopes.FUSIONTABLES).asJava
      )
    ).setApplicationName("MOXBOX-Sample").build()
  }
}

秘密鍵ファイル (JSON) から GoogleCredential インスタンスを生成し、それを HttpRequestInitializer に指定して Fusiontables インスタンスを作成する。

既存の Fusion Tables を利用する

Fusion Table の共有設定でアクセス権限を与えることで Google Drive 上に存在している Fusion Table をアプリケーションから使用することができる。

アクセス権限は Fusion Table の「Share」を開き「招待」欄にアプリケーションのサービスアカウント ID (xxxx@yyyy-00000.iam.gserviceaccount.com) を指定する。アプリケーションから書き込みを行わない場合は「閲覧者」権限に設定しておくと良いだろう。

共有リンクの URL に含まれている docid パラメータ値は tableId である。アプリケーションから SQL を発行する時にテーブル名として使用するためどこかに控えておく。

アクセスが許可された Fusion Tables はアプリケーションから一覧として参照することができる。

Option(fusiontables.table().list().execute().getItems).map(_.asScala).getOrElse(List.empty).foreach { t =>
  println(t)
}

この出力結果はテーブル ID と各カラムの定義を含む JSON である。

{"columns":[
  {"columnId":0,"description":"ユーザID","formatPattern":"NONE","kind":"fusiontables#column","name":"ID","type":"STRING","validateData":false},
  {"columnId":5,"description":"ユーザ名","formatPattern":"NONE","kind":"fusiontables#column","name":"USER_NAME","type":"STRING","validateData":false},
  {"columnId":2,"formatPattern":"NONE","kind":"fusiontables#column","name":"LOCATION","type":"LOCATION","validateData":false},
  {"columnId":3,"description":"登録日時","formatPattern":"NONE","kind":"fusiontables#column","name":"REGISTER","type":"DATETIME","validateData":false}
],"isExportable":true,"kind":"fusiontables#table","name":"Fusion Tables Sample for MOXBOX","tableId":"1o9p....06G"}

Fusion Tables での新規テーブル作成は上記のような JSON を作成する必要があり煩雑である。先に既存のテーブルでの使い方を慣らした方が良いだろう。このページでは以下のスキーマを持つテーブルを例として使用している。

カラム名 タイプ
ID STRING (Text)
NAME STRING (Text)
LOCATION LOCATION (Location)
REGISTER DATETIME (Date/Time)

データ操作

Fusion Tables のデータ操作は SELECT, INSERT, UPDATE, DELETE の SQL に似た構文を使用することができる。SQL 構文の詳細は Google のリファレンスを参照。

以下のサンプルコードでは省略しているが、一般的な RDBMS と同様に SQL 部分の値の埋め込みはエスケープが必要だ。文字列中の引用記号 '\' に置き換えることでエスケープを行うことができる。

INSERT 命令

説明の順序として最初にデータを作成しよう。RDBMS の SQL と異なりテーブル名ではなく tableId を使用する。これは Fusion Table の共有 URL からコピーするか参照可能なテーブル一覧から参照する。

val tableId = "1o9p....06G"
val batch = fusiontables.batch()
val callback = new JsonBatchCallback[Sqlresponse] {
  override def onFailure(e:GoogleJsonError, responseHeaders:HttpHeaders):Unit = println(s"Failure: $e")
  override def onSuccess(t:Sqlresponse, responseHeaders:HttpHeaders):Unit = println(s"Success: $t")
}
case class User(id:String, name:String, location:(Double, Double), register:Date = new Date())
Seq(
  User("82502402-6cf4-4da2-a2f5-5d751f6d08df", "北海道 太郎", (43.064308, 141.346833)),
  User("f41ba42b-de83-4aef-a7d8-e4b7489f39eb", "仙台 次郎", (38.268197, 140.869416)),
  User("d7e9c328-1ce8-4462-bb7b-6c3b9615ee30", "東京 三郎", (35.689620, 139.692106)),
  User("8b5f16b4-6c1f-4286-88a8-662add7b907f", "名古屋 四郎", (35.180236, 136.906689)),
  User("8f8c655e-02c4-4849-8975-593a1d807240", "大阪 五郎", (34.693736, 135.502206)),
  User("0e78b8c4-233e-4a2d-b94f-8ba296169c47", "博多 六郎", (33.590119, 130.401716))
).foreach{ user =>
  fusiontables.query().sql(s"INSERT INTO $tableId(ID,USER_NAME,LOCATION,REGISTER) VALUES('${user.id}','${user.name}','${user.location._1} ${user.location._2}','${user.register}')").queue(batch, callback)
}
batch.execute()

上記のように一度に複数の SQL を実行する場合はリクエスト数を節約するためにバッチで行うことを推奨する。処理が成功すれば rowid を含む Sqlresponse がコールバックされ右図のようにデータが追加されるだろう。

なお Location 型のカラムには KML で定義されている書式の Point, Line, Polygon を使用することができる。上記コードの LOCATION 値は以下のように書き換えが可能である (コンマ追加と緯度/経度の入れ替えに注意)。

'<Point><coordinates>${user.location._2},${user.location._1}</coordinates></Point>'

SELECT 命令

Fusion Tables の SELECT 命令は WHERE, GROUP BY, ORDER BY, OFFSET, LIMIT を使用することができる。ただし一般的な RDBMS よりも機能は大幅に限られている。特に WHERE 句で OR が使用できないことに注意。RDBMS ではなく KVS と考えるのが良い。

val res = fusiontables.query().sqlGet(
  s"SELECT ROWID,ID,USER_NAME,LOCATION,REGISTER FROM $tableId WHERE USER_NAME STARTS WITH '大阪'"
).execute()
println(res)
val rowId = res.getRows.get(0).get(0)
println(s"ROWID=$rowId")

ROWID カラムは全てのテーブルに暗黙的に付与されているカラムで行の削除時や更新時に必要となる。実行結果は以下のような JSON をラップした Sqlresponse インスタンスである。

{ "columns":["rowid","ID","USER_NAME","LOCATION","REGISTER"],
  "kind":"fusiontables#sqlresponse",
  "rows":[
    ["40","8f8c655e-02c4-4849-8975-593a1d807240","大阪 五郎","34.693736 135.502206","Tue Nov 28 15:47:45 JST 2017"]
  ]}
ROWID=40

Fusion Tables の SELECT は地理検索に対応しており、特定の円や矩形に含まれる点を持つ行を参照することができる。詳細はリファレンスを参照。

UPDATE / DELETE 命令

UPDATEDELETE 命令で WHERE に指定できるのは rowid のみであるため条件に一致する行を SELECT で取得しなければならない。ここでは先の SELECT で取得した変数 rowId を使用する。

fusiontables.query().sql(
  s"UPDATE $tableId SET USER_NAME='大阪 万次郎' WHERE ROWID='$rowId'"
).execute()
// => {"columns":["affected_rows"],"kind":"fusiontables#sqlresponse","rows":[["1"]]}

実行結果は Sqlresponse に影響のあった件数が返される。Google Drive でテーブルを参照すると更新した行に新しい値が設定されているのが分かる。

行の削除も更新と同様に可能である。

fusiontables.query().sql(
  s"DELETE FROM $tableId WHERE ROWID='$rowId'"
).execute()
// => {"columns":["affected_rows"],"kind":"fusiontables#sqlresponse","rows":[["1"]]}

テーブル操作

Fusion Tables のテーブル操作は一般的な RDBMS での CREATE TABLE のような DDL は用意されておらずテーブル定義をコードで記述する。

テーブルの作成

テーブル作成は ColumnTable を構築して table().insert() を使用する。

import com.google.api.services.fusiontables.model.{Column, Table}

val key = new Column()
key.setName("KEY")
key.setType("STRING")

val value = new Column()
value.setName("VALUE")
value.setType("STRING")

val newTable = new Table()
newTable.setColumns(util.Arrays.asList(key, value))
newTable.setName("NEW TABLE")
newTable.setIsExportable(false)
val result = fusiontables.table().insert(newTable).execute()
println(result)

実行結果は以下の通り。後のテーブル操作のために tableId を記録しておく必要がある。

{ "columns":[
    {"columnId":0,"formatPattern":"NONE","kind":"fusiontables#column","name":"KEY","type":"STRING","validateData":false},
    {"columnId":1,"formatPattern":"NONE","kind":"fusiontables#column","name":"VALUE","type":"STRING","validateData":false}
  ],"isExportable":false,"kind":"fusiontables#table","name":"NEW TABLE","tableId":"1HbpZ...IeB"}

パーミッションの変更

アプリケーションから新規に作成したテーブルはサービスアカウントのオーナーである自分からも参照することができない。これでは不便であるため Fusion Tables ファイルを自分に共有しよう。これは Google Drive REST APIPermissions を利用する。

まず Fusion Table を有効にしたのと同じように Google API Console から Google Drive API を有効化する。次に build.sbt に Maven リポジトリに登録されている Google Drive API のライブラリ を追加する。

"com.google.apis" % "google-api-services-drive" % "v3-rev90-1.21.0"

最後に共有設定 (パーミッション) を追加するコードを記述して実行すれば良い。tableId はそのまま Google Drive での fileId として使用することができる。以下のコードでは自分のメールアドレスを指定して writer (編集者) 権限を付与している (同時に該当アカウントへのメール通知を行っている)。

import com.google.api.services.drive.{Drive, DriveScopes}
import com.google.api.services.drive.model.Permission

val permission = new Permission()
permission.setEmailAddress("xxxx@gmail.com")
permission.setType("user")
permission.setRole("writer")
val drive = new Drive.Builder(
  new NetHttpTransport(), new JacksonFactory(),
  credential.createScoped(util.Arrays.asList(DriveScopes.DRIVE))
).setApplicationName("MOXBOX-Sample").build()
drive.permissions().create(newTableId, permission).setSendNotificationEmail(true).execute()
// => {"id":"094...509","kind":"drive#permission","role":"writer","type":"user"}

認証情報の credential はそのまま使用できるが Drive スコープ版を作成する必要がある。

パーミッションの変更の他に Google Drive API からテーブルの有無を確認したり削除を行っても良いだろう。

テーブルの削除

テーブルの削除はテーブル ID を指定するだけなので特に説明は必要ないだろう。

fusiontables.table().delete(tableId).execute()
// => null

また前述のとおり Drive API を使用してファイルとして削除することもできる。

参考文献