風柳メモ

ソフトウェア・プログラミング関連の覚書が中心

【覚書】Google Fusion Tables を使えるようにする手順

データベースと地図とのお手軽な連携方法を調べていて、Google Fusion Tables と Google Maps の組み合わせが使えないかなあ、と思ったので、試してみることに。
……が、実は前提となる Google Drive 自体をほぼ使ったことが無かったこともあって、とりあえずテーブルを作成する状態まで持ってくるのにも少々とまどったため、忘れないうちにメモ。

Google ドライブに Google Fusion Tables のアプリを追加(接続)(初回のみ)

Googleドライブのマイドライブから、
[新規] → その他 → +アプリを追加
とたどって、
f:id:furyu-tei:20150819211106p:plain
By Google から
f:id:furyu-tei:20150819211111p:plain
Fusion Tables を探し出し、
f:id:furyu-tei:20150819211114p:plain
接続する。
f:id:furyu-tei:20150819211118p:plain
この手順は一度実施するだけで良い。


テーブルの作成

Googleドライブのマイドライブから、
[新規] → その他 → Google Fusion Tables
を選択し、
f:id:furyu-tei:20150819211122p:plain

のいずれかを選択して[Next]を押すと、
f:id:furyu-tei:20150819211125p:plain
テーブルが作成される。
f:id:furyu-tei:20150819211130p:plain

【付記】Two column location の設定方法

Edit → Change columns
にて、カラムを追加・削除したり、名前や型(Type)等を編集したりできる。
f:id:furyu-tei:20150819211134p:plain

カラムの Type で "Location" を選んだ場合には、地名や住所、緯度・経度の組み合わせ(カンマ区切り)などを入れることができる(これは Map の方にも反映される)。
このとき、緯度・経度しか使用しない場合には、「□ Two column location」にチェックを入れて、緯度(Latitude)、経度(Longitude)として使用するカラム名をそれぞれ指定すればよいのだが、最初これを行おうと、Latitude カラムを追加してみると、
f:id:furyu-tei:20150819211139p:plain
Type を Location に変更しても、「□ Two column location」が選択できない(灰色表示)。

いろいろ試してみたところ、先に Longitude に該当するカラムを Type = Number で作成してから、
f:id:furyu-tei:20150819211149p:plain
一旦[Save]し、再び、Edit → Change columns から開くと、
f:id:furyu-tei:20150819211154p:plain
今度は選択できるようになっているので、チェックし、Latitude/Longitudeにそれぞれ対応するカラム名を選択して(ついでに、不要になったLocationカラムは削除して)、再度[Save]する。


このようにカラムを設定してから、実際に緯度・経度を指定したデータ(行)を作成してみると、
f:id:furyu-tei:20150819211158p:plain
"Map of Location" タブで、当該データがプロットされているのを確認できる。
f:id:furyu-tei:20150819211203p:plain


【覚書】Google Fusion Tables に Google Apps Script でアクセスする手順

ひとまず Google Fusion Tables が使えるようになったが、何らかのプログラムからアクセスする方法を調べないと……ということで、Google Apps Scriptなるもの(実はこれも初めていじる)で試してみる。

Google ドライブに Google Fusion Tables のアプリを追加(接続)(初回のみ)

Googleドライブのマイドライブから、
[新規] → その他 → +アプリを追加
とたどって、
f:id:furyu-tei:20150819211106p:plain
By Google から
f:id:furyu-tei:20150819211111p:plain
Google Apps Script を探し出し、
f:id:furyu-tei:20150819211607p:plain
f:id:furyu-tei:20150819211616p:plain
接続する。
f:id:furyu-tei:20150819211619p:plain
この手順は一度実施するだけで良い。


Google Apps Scriptのスクリプトを作成し、Fusion Tables にアクセスできるよう設定

Googleドライブのマイドライブから、
[新規] → その他 → Google Apps Script
と選択し、
f:id:furyu-tei:20150819211624p:plain
「空のプロジェクト」を選んで[閉じる]を押すと、
f:id:furyu-tei:20150819211629p:plain
プロジェクトが作成される。

ここで、メニューの
リソース → Google の拡張サービス...
を選ぶと、
f:id:furyu-tei:20150819211640p:plain
プロジェクト名を決めていないときはここで尋ねられるので、適当に名前をつけて[OK]を押す。
f:id:furyu-tei:20150819211645p:plain
Google の拡張サービスの中から、Fusion Tables を探して、
f:id:furyu-tei:20150819211649p:plain
右側の□をクリックし、ON(緑)の状態にする。
f:id:furyu-tei:20150819211653p:plain
ここで、[OK]はまだ押さずに、続けて

これらのサービスは Google デベロッパー コンソールでも有効になっている必要があります。

のリンクをクリックし、Google Developers Consoleを開く。
すると、APIと認証 → API の API ライブラリが開いているはずなので、「Fusion Tables API」を探し、
f:id:furyu-tei:20150819211658p:plain
「Fusion Tables API」をクリックした画面で、[APIを有効にする]ボタンを押す。
f:id:furyu-tei:20150819211702p:plain
その後、Google Apps Scriptの画面に戻ると、そのスクリプトでは Fusion Tables API が使用できるようになる。

簡単なスクリプトの実行

Googleドキュメント上の Fusion Tables のテーブルにアクセスして、行内容を表示するだけのサンプルスクリプト。

var tableId = 'your-table-id';

function getRows(request) {
  var sql = 'SELECT * FROM ' + tableId + ' LIMIT 10';
  
  var result = FusionTables.Query.sqlGet(sql);
  for (var row_number=0; row_number < result.rows.length; row_number++) {
    var columns = [];
    for (var column_number=0; column_number < result.columns.length; column_number++) {
      columns.push(result.columns[column_number] + '=' + result.rows[row_number][column_number]);
    }
    Logger.log(columns.join(', '));
  }
}

ここで、"your-table-id" の部分には、自分で作成した Fusion Tables のテーブルの画面右上にある[Share]ボタンを押して表示される共有リンク
f:id:furyu-tei:20150819211724p:plain
の、"……?docid=~" の~部分をコピーして貼り付ける。

これを実行すると、
f:id:furyu-tei:20150819211707p:plain
承認を求められるので、[続行]を押し、
f:id:furyu-tei:20150819211712p:plain
許可のリクエスト画面にて、[承認]を押す。
f:id:furyu-tei:20150819211717p:plain
実行が終わり、メニューから
表示 → ログ
を選んで、
f:id:furyu-tei:20150819211720p:plain
実行結果が表示されていれば、無事 Fusion Tables のテーブルへのアクセスが成功している。
f:id:furyu-tei:20150819211728p:plain

【覚書】Google Fusion Tables に Python でアクセスする手順

Google Fusion Tables の更新は、実際には、自前のLinuxサーバー等から自動的に行うことを考えているため、まずは Linux 端末上から Fusion Tables にアクセスできる方法を調べる必要がある。
この場合、Linux 端末側は、OAuth 2.0 クライアントとして Fusion Tables API を使用することになるらしい。developers.google.com
で公開されている各プログラミング言語用のライブラリを用いることで、アクセスできるのだと思われる。
とりあえず、ある程度使い慣れている Python 2.7 によるアクセスを行うことを試みた。
いろいろと調べつつ、手探りで試してとりあえずアクセスは出来たが、これが正しい方法なのかどうかは自信がない。もっとスマートなやり方がありそうな気がするが……。

OAuth 2.0 クライアントID&シークレットの取得

Google Developers Consoleを開き、Fusion Tables へのアクセス用に、新しいプロジェクトを作成する。
f:id:furyu-tei:20150819212036p:plain
f:id:furyu-tei:20150819212043p:plain
f:id:furyu-tei:20150819212048p:plain


作成したプロジェクトを開き、
f:id:furyu-tei:20150819212051p:plain
左のメニューの
APIと認証 → API
から、Fusion Table API を選んで
f:id:furyu-tei:20150819212054p:plain
[APIを有効にする]を押す。
f:id:furyu-tei:20150819212100p:plain


次に、同じく
APIと認証 → 認証情報
から、
[認証情報を追加] → OAuth 2.0 クライアント ID
を選択し、
f:id:furyu-tei:20150819212111p:plain
[同意画面を設定]ボタンを押して
f:id:furyu-tei:20150819212117p:plain
サービス名を適当につけて入力し、[保存]を押す。
f:id:furyu-tei:20150819212123p:plain


「アプリケーションの種類」は「○ その他」を選択し、[作成]ボタンを押すと、
f:id:furyu-tei:20150819212128p:plain
OAuth 2.0 のクライアントID及びクライアントシークレットが発行されるので、[OK]を押す。
これは後からでも確認できるため、特にコピーしておく必要などはない。
f:id:furyu-tei:20150819212142p:plain

OAuth 2.0 クライアントID情報は、右側にあるダウンロードボタンにて、JSON形式でダウンロードできる。
f:id:furyu-tei:20150819212153p:plain


Linux 端末(OAuth 2.0 クライアント)側の準備

Installation  |  API Client Library for Python  |  Google Developers
に従い、

sudo pip install --upgrade google-api-python-client

のようにして、google-api-python-client をインストールしておく。


Python で Fusion Tables にアクセスするサンプルコード(sample_fusiontables.py)

上記でダウンロードしたJSON(OAuth 2.0 クライアントID情報)ファイルを、
client_secrets.json
という名前にして同じディレクトリに配置した状態で実行すると、アクセス可能な Fusion Tables の最初のテーブルIDを取得し、当該テーブルの行列を表示するサンプル。

# -*- coding: utf-8 -*-

# OAuth 2.0 for Devices (sample code)
# https://github.com/google/oauth2client/blob/master/samples/oauth2_for_devices.py

# Using OAuth 2.0 for Devices
# https://developers.google.com/accounts/docs/OAuth2ForDevices

# Fusion Tables REST API
# https://developers.google.com/fusiontables/docs/v2/reference/

from __future__ import print_function

import sys
import json
import time
import datetime
import httplib2
import traceback
from pprint import pprint
from oauth2client import client
from googleapiclient.discovery import build

def get_credentials(scopes, client_secret_file = None, credential_file_in = None, credential_file_out = None): #{
  def load_credential_file(credential_file_in):
    if not credential_file_in:
      return None
    try:
      fp = open(credential_file_in, 'rb')
      credentials = client.OAuth2Credentials.new_from_json(fp.read())
      fp.close()
      if credential_file_in != credential_file_out:
        save_credential_file(credential_file_out, credentials)
      return credentials
    except Exception, error:
      #print(traceback.format_exc())
      pass
    return None
    
  def save_credential_file(credential_file_out, credentials):
    if not credential_file_out:
      return
    fp = open(credential_file_out, 'wb')
    fp.write(credentials.to_json())
    fp.close()
  
  credentials = load_credential_file(credential_file_in)
  if credentials:
    return credentials
  
  client_secrets = json.load(open(client_secret_file, 'rb'))
  client_id = client_secrets['installed']['client_id']
  client_secret = client_secrets['installed']['client_secret']
  
  while not credentials:
    flow = client.OAuth2WebServerFlow(client_id, client_secret, ' '.join(scopes))
    
    # Step 1: get user code and verification URL
    # https://developers.google.com/accounts/docs/OAuth2ForDevices#obtainingacode
    flow_info = flow.step1_get_device_and_user_codes()
    
    print('Verification URL: {0}'.format(flow_info.verification_url));
    print('User code: {0}'.format(flow_info.user_code));
    
    user_code_expiry = flow_info.user_code_expiry
    interval = flow_info.interval
    
    while datetime.datetime.now() < user_code_expiry:
      print('\r{0:>4} seconds remaining'.format( (user_code_expiry - datetime.datetime.now()).seconds ), end = '')
      sys.stdout.flush()
      # Step 2: get credentials
      # https://developers.google.com/accounts/docs/OAuth2ForDevices#obtainingatoken
      try:
        credentials = flow.step2_exchange(device_flow_info = flow_info)
        if credentials:
          break
      except Exception, error:
        #print(traceback.format_exc())
        credentials = None
      time.sleep(interval)
    print('')
  
  save_credential_file(credential_file_out, credentials)
  
  return credentials
#} // end of get_credentials()


if __name__ == '__main__': #{
  # https://console.developers.google.com/project/<project name>/apiui/credential (Download JSON)
  CLIENT_SECRET_FILE = 'client_secrets.json'
  
  # Allowed scopes
  # https://developers.google.com/identity/protocols/OAuth2ForDevices#allowedscopes
  SCOPES = ('https://www.googleapis.com/auth/fusiontables',)
  
  CREDENTIAL_FILE = 'credentials.json'
  
  credentials = get_credentials(
    client_secret_file = CLIENT_SECRET_FILE,
    scopes = SCOPES,
    credential_file_in = CREDENTIAL_FILE,
    credential_file_out = CREDENTIAL_FILE,
  )
  
  print('Access token: {0}'.format(credentials.access_token))
  print('Refresh token: {0}'.format(credentials.refresh_token))
  
  # Get Fusion Tables service
  # https://developers.google.com/accounts/docs/OAuth2ForDevices#callinganapi
  fusiontables = build(
    serviceName = 'fusiontables',
    version = 'v2',
    http = credentials.authorize( httplib2.Http() ),
  )
  
  # Fusion Tables REST API
  # https://developers.google.com/fusiontables/docs/v2/reference/
  
  # https://developers.google.com/fusiontables/docs/v2/reference/#Table
  Table = fusiontables.table()
  
  # https://developers.google.com/fusiontables/docs/v2/reference/#Column
  Column = fusiontables.column()
  
  # https://developers.google.com/fusiontables/docs/v2/reference/#Template
  Template = fusiontables.template()
  
  # https://developers.google.com/fusiontables/docs/v2/reference/#Style
  Style = fusiontables.style()
  
  # https://developers.google.com/fusiontables/docs/v2/reference/#Query
  Query = fusiontables.query()
  
  # https://developers.google.com/fusiontables/docs/v2/reference/#Task
  Task = fusiontables.task()
  
  # ===== Samples
  print('***** Tables')
  table_list = Table.list().execute()
  pprint(table_list)
  tableId = table_list['items'][0]['tableId']
  
  print('***** Columns & Rows')
  rows = Query.sql(
    sql = 'SELECT * FROM {0}'.format(tableId)
  ).execute()
  pprint(rows)

Shell 上から、

$ ls
client_secrets.json  sample_fusiontables.py
$ python ./sample_fusiontables.py

のように実行すると、初回には、標準出力上に

Verification URL: https://www.google.com/device
User Code: XXXX-XXXX
xxxx seconds remaining

のように表示されて待ち状態に入る。

手動で「Verification URL」に示された URL にブラウザでアクセスすると、(場合によってはアカウント選択・ログイン画面の後で)端末に表示されるコードの入力を促されるため、「User Code」で示されたコードを入力し、[続行]を押す。
f:id:furyu-tei:20150819212158p:plain
Fusion Tables の管理の許可を求められるため、[承認する]を押す。
f:id:furyu-tei:20150819212207p:plain


すると、しばらくしてサンプルプログラム側が承認を認識し(Access token・Refresh tokenを取得し)、結果が表示される。


なお、Access token・Refresh tokenを含む情報は、カレントディレクトリ上の
credentials.json
というファイルに書き込まれ、

$ ls
client_secrets.json  credentials.json  sample_fusiontables.py

次回実行時にはこれを読み込んで使用するため、基本的には、上記のブラウザによる手動の承認手順は初回のみでよい。
この動作の詳細は、上記ソースコード中の get_credentials() 関数の定義及び呼び出し箇所を参照のこと。