ラベル google の投稿を表示しています。 すべての投稿を表示
ラベル google の投稿を表示しています。 すべての投稿を表示

2011/02/09

Google Maps API v3 でカスタムマップを作成

暫く前に、「googlemapでドラクエ」ということで、ドラゴンクエストの世界地図を Google Maps で表示するサイトが話題になりました。
当時、コレを見て感動したのを覚えています。

今では Google Maps API も v3 となり、v2 は正式に廃止され、アップデートすることが推奨されています。

ということなので、カスタムマップの作成を v3 でやってみます。

大きい画像の用意

まずは、拡大時に耐えるような大きな画像を用意します。

1900年に作成された日本の世界地図 から拝借しました。

タイル(tile)を作成

Google Maps は、それぞれのズームレベルに合わせて 縦横 256px の画像(タイル)を地球全面に覆う仕組みになっています。
例えば、ズームレベル 1 では、256x256 px の画像を縦横 2枚、計 4枚のタイルにより構成されます。
ズームレベル 2 では、縦横それぞれ 2 倍の、計 16枚のタイルで構成されます。

GMIC - The GMap Image Cutter

というわけで、多数のタイルが必要になるのですが、自力でカットしていくのははっきり言って無理があります。

そこで GMIC というとても便利なツールの登場です。

このツールを使えば、用意した画像を Google Maps に合わせたタイルにカットしてくれます。しかも、解像度から最大ズームレベルまで判断してくれます。

使用方法も簡単で、ダウンロードした zip を解凍し、GMapImageCutter.batを実行(Windows の場合。その他は .sh)して起動させます。

起動したら、File を Open して、 Create するだけ!簡単です。動作も高速であっという間に作業が完了します。

出力ファイルには、タイル画像だけではなく、作成したタイルを表示するためのサンプル HTML も出力されます。
大きな画像を Google Maps 的に見たい!というなら、これだけで OK ですね。

V3

GMIC より出力された HTML は、残念ながら今現在は v2 の Google Maps API を使用しています。

そこでやっと本題の、カスタムマップを v3 で書き換えてみます。

ポイントは、ImageMapType というクラスを使うことで、一から MapType インターフェースを実装する手間を省いています。

サンプルは下記リンクから!

Google Maps API v3 カスタムマップサンプル

 

参考:

2009/09/07

Google App Engine Oil で twitter もどき STEP 1

前回、Webアプリケーションをさくっと手軽に構築できてしまう Google App Engine (GAE) を試してみましたが、今回は、もう少しまともなアプリケーションを作ってみます。

作成する前に、つい先日 GAEO が 0.3 にバージョンアップしているようなので、早速最新のリリースを試してみましょう。

バージョンアップ作業は、環境変数のパスを新しいバージョンのものに変更しただけです。

Twitter

Twitter は、140文字以下の短いメッセージをつぶやきあうシンプルなサービスです。現在とても注目されているサービスですね。
シンプルでわかりやすいですので、今回は Twitter もどきのアプリケーションを作成してみます。

mockker

それでは、Twitter もどきの「mockker」というプロジェクトを作成します。
とりえあず今回は、データの登録ができればOKとします。また、アカウントは Google App Engine の ユーザーサービスを使用し、Google アカウントと連携させます。

gaeo.py mockker
cd mockker

まずは、つぶやきの scaffold を作成します。

gaeogen.py scaffold status create show "text:StringProperty()" "user:UserProperty()" "created_at:DateTimeProperty(auto_now_add=True)"

ルーティング

デフォルトのルートへのルーティングを、welcome/index から status/index へ変更します。

gaeo/dispatch/router.py
class Router:

  """ Handles the url routing... """


  class __impl:

    def __init__(self):
      self.__routing_root = {'controller': 'status',
                   'action': 'index'}

model

scaffold により、 application/model/status.py が作成されます。

application/model/status.p
from google.appengine.ext import db
from gaeo.model import BaseModel, SearchableBaseModel

class Status(BaseModel):
  created_at = db.DateTimeProperty(auto_now_add=True)
  text = db.StringProperty()
  user = db.UserProperty()

controller

scaffold により作成された status コントローラを次のように修正します。

application/controller/status.py
import cgi
import logging

from google.appengine.ext import db
from google.appengine.api import users

from gaeo.controller import BaseController

from model.status import Status

class StatusController(BaseController):
    def create(self):
        r = Status(
            # Uncomment all required properties here.
            # text = self.params.get('text', None),
            # created_at = self.params.get('created_at', None),
            user = users.get_current_user(),
        )
        for prop in Status.properties():
            if prop in self.params:
                setattr(r, prop, self.params.get(prop))
        r.put()
        self.flash['notice'] = u"独り言を更新したよ"
        self.redirect('/')

    def index(self):
        notice = self.flash.get('notice', '')
        self.msg = notice
        if self.current_user:
            query = Status.all().filter('user = ', self.current_user)
            query.order("-created_at")
            self.result = query.fetch(limit=10)

    def show(self):
        r = Status.get(self.params.get('id'))
        if r:
            for prop in Status.properties():
                setattr(self, prop, getattr(r, prop))
        else:
            self.redirect('/')

ユーザー認証のため、base コントローラの before_action を次のように修正します。
これにより、全てのアクションの前にユーザー認証が実行されます。

gaeo/controller/__init__.py
from google.appengine.api import users

  def before_action(self):
    self.current_user = users.get_current_user()
    if not self.current_user:
      self.signed_in = False
      self.login_url = users.create_login_url(self.request.uri)
      #self.redirect(users.create_login_url(self.request.uri))
    else:
      self.signed_in = True
      self.logout_url = users.create_logout_url(self.request.uri)
      self.nickname = self.current_user.nickname()

view

view を次のように修正します。

application/templates/base.html
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
  "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  <title>{% block title %}{% endblock %}</title>
  <link rel="stylesheet" href="/css/common.css" type="text/css" media="screen" />
  {% if signed_in %}
  <link rel="stylesheet" href="/css/login.css" type="text/css" media="screen" />
  {% endif %}
  <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>
  <script type="text/javascript" src="/js/application.js"></script>

</head>
<body>
<div id="header">
  <div id="header_inner">
  <h3><a href="/" title="Mockker">Mockker</a></h3>
  {% if signed_in %}
  <ul id="nav">
    <li><a href="{{ logout_url }}" title="Logout">Logout</a></li>
  </ul>
  {% else %}
  <ul id="nav">
    <li><a href="{{ login_url }}" title="Login">Login</a></li>
  </ul>
  {% endif %}
  </div>
</div>
<div id="content">
  <div id="content_inner">
  {% if msg %}
  <div class="flash">
    <div class="notice">
    <p>{{ msg }}</p>
    </div>
  </div>
  {% endif %}
  {% if signed_in %}
  <div id="update">
    <form method="post" action="/status/create">
    <span id="countdown">140</span>
    <label for="text">何か言いたいことは?</label>
    <textarea id="text" name="text" rows="10" cols="10"></textarea>
    <input type="submit" name="update" value="Update" />
    </form>
  </div>
  {% endif %}

  {% block content %}{% endblock %}
  </div>
</div>

<div id="footer">
  <div id="footer_inner">
  <div class="column">
    <ul>
    <li><a href="/" title="Home">Home</a></li>
    </ul>
  </div>
  </div>
</div>
</body>
</html>
application/templates/status/index.html
{% extends "../base.html" %}

{% block title %}StatusController#index{% endblock %}

{% block content %}

<div id="statuses">
{% for r in result %}

  <div id="status_{{ r.key }}" class="status">
  <div class="info">

    <p class="who_when">
    {{ r.user.nickname }}<br />
    {{ r.created_at }}
    </p>

    <p class="actions">
    </p>
  </div>

  <div class="text">
    {{ r.text }}
  </div>
  </div>
{% endfor %}
</div>


{% endblock %}

あとは、Javascript や CSS などで、見栄えを整えます。

実行

dev_appserver.py .

登録したデータをクリアしたい場合は、「--clear_datastore」オプションをつけて実行します。

dev_appserver.py --clear_datastore .

以上で、自分だけがつぶやくことができる、独り言アプリケーションの完成ですw
次回は、他人のつぶやきを表示できるようにしてみたいと思います。

2009/08/04

Google App Engine Oil

Google App Engine Oil

Webアプリケーションをさくっと手軽に構築できてしまう Google App Engine (GAE) ですが、この Google 製のエンジンに、あるオイルを入れてやると、さらに激速なマシンができるようです。

Google App Engine Oil(GAEO) というこのオイルは、Ruby on Rails を参考にして作られた App Engine 専用の レーシングスペックのエンジンオイル(Webアプリケーションフレームワーク)です。

GAEO をインストール

まず、GAEO は GAE 上で実行されますので、予め Python や、GAE SDK をインストールします。
なお、以下は Windows での解説です。

プロジェクトホーム からパッケージ(gaeo-0.2.1.zip など)をダウンロードし、好きなところに解凍します。

GAEO スクリプトを利用するために、解凍先の bin ディレクトリへのパスを、環境変数 PATH に追加します。

パスを設定しましたら、gaeo.py コマンドを実行し、下記のようになるとインストール完了です。

>gaeo.py
Usage: C:\<install dir>\bin\gaeo.py <project name>

Hello GAEO

では、早速 Hello GAEOプロジェクトを作ってみます

GAEO でのプロジェクトを作成するには、作業ディレクトリで、次のコマンドを実行します。

gaeo.py hello

hello プロジェクトを作成すると、hello ディレクトリには、下記のコンテンツが生成されます。Rails そっくりです。

  • app.yaml, favicon.ico, main.py

    app.yaml と main.py は、GAE のメイン設定ファイルです。

  • application/

    アプリケーションのコードの置き場所。基本的にこの中のファイルを編集していきます。

  • assets/

    javascript や css ファイルの置き場所。app.yaml で設定可能です。

  • gaeo/

    GAEO のコアライブラリ。GAEO のアップグレードは、このディレクトリのファイルを入れ替えるとOKです。

  • plugins/

    プラグインのインストールディレクトリです。

アプリケーションの起動と確認

次のコマンドで GAE を起動し、作成したアプリケーションの動作を確認します。
なお、コマンドはアプリケーションのルートディレクトリで実行します。

hello>dev_appserver.py .
 Running application hello on port 8080: http://localhost:8080

http://localhost:8080/ にアクセスして、「It works!!」と表示されればOKです。

なかなか刺激的な体験ですが、次はちょっとしたアプリケーションを作ってみたいと思います。

Google App Engine Oil で twitter もどき
Google App Engine Oil で twitter もどき STEP 1

参考:

2009/06/15

Google Developer Day 2009

Google Developer Day

今年も Google Developer Day に参加してきました。

すでに恒例のイベントとなっていますが、年々内容が洗練され、とてもすばらしい内容になってきました。

今回の主な内容は、次のもの。

Google Developer Day

なかでも、HTML5Google Wave はあらたな時代を感じさせるすばらしいものでした。
ブラウザだけで(HTML + Javascript)、2D や 3D といったリッチな表現を可能にする、HTML5。
これまでの メール 主体のコミュニケーションを大きく変えてしまうパワーがある、Google Wave。
実際に普及となるとまだ先だとは思いますが、

Android についても、もうすぐ日本でも docomo から発売されますので、これから非常に注目されるものです。

Open Social は、ついに日本最大の SNS である mixi が完全対応ということで、多くのユーザにリーチできるという点で、マーケティング的にも非常に魅力的なものになりました。
Android と Open Social の組み合わせは、非常に刺激的なものです。

そして、Google AppsGoogle App Engine の組み合わせによる、Enterprise なアプローチは、クラウド化する今日において、対企業へ非常に魅力的なソリューションを提供できるようになります。

Google Maps は、久しぶりのバージョンアップにより、Ver 3 になります。
iPhone や、Android といったモバイル端末上で快適に動作するように最適化がなされるようです。
GPS が普及し、地図分野はますます便利になっていきますね。

Google Developer Day 2009 Android

今回の GDD では、Google I/O 同様サプライズとして、Google Dev Phone の新版が無料配布されました。
すでにあった Dev Phone 1 (Android 1.0) を、Android 1.5 にアップデートしようかと思っていたところだったので、ちょうどいいタイミングです。
しかも、ロケールに日本語があって、日本語での文字入力も可能。すばらしいです。
これで、Android 1.5 でも実機テストが可能ですね。

会場でも、iPhone を片手に講演を聴いている方が多数いて、やはり、昨年とは会場の雰囲気も違いました。
来年はきっと、僕も Android を片手に講演を聴いているでしょう・・。

思いついたアイディアが、たとえ先見の明があったとしても、それが時期尚早だったりすることがあります。
しかし、それが今ならなんだかいけそうな気がする!と、今年の GDDD は、そんな気になりました。

今一度、過去のアイディアを整理してみるのもいいかもしれません。

2009/04/17

Google Earth で、ツアーを作ってみる

Google Earth

先日 TechCrunch より、 Google Earthのフライスルーがブラウザで閲覧できるようになった という衝撃の記事を発見しました。



今週初め、GoogleはGoogle Earthツアーをブラウザから直接閲覧できるプラグインをリリースした。2月に公開されたGoogle Earthのリリース5.0でも導入されたツアー機能を使えばGoogle Earthで表示できる場所のどこででもバーチャル・フライスルーを作成することができる。これによってなかなか印象的な作品を作り出すこともできる。Googleではいくつものベストツアーをギャラリーに集めており、ハドソン川に緊急着水したFlight 1549の歴史的なフライトの再現やサンフランシスコの疾風ツアーが紹介されている。



というわけで、早速試してみました。

ビデオによるツアーの作成方法は こちら から。




そして、実際に作成してみたツアーが下のものです。プラグインをインストールすると見えるようになります。





次回の Google Developper Day 2009 の開催地である、パシフィコ横浜 へ一気にジャンプするだけですが、ブラウザだけで見れるのはなかなかですね。


サイトへの埋め込みは、 Embedded Tour Player Google gadget で、Google Earth で作成した KML ファイルを指定するだけ。あとは作成されたガジェットのコードをペーストすればいいんですね。
これはおもしろいです!

2009/01/23

[GAE]Google App Engine を使ってみる Hello, webapp World!

前回は、単純な CGI を試してみましたが、今回は、Google App Engine に提供されている webapp というフレームワークを使ってみます。



Hello, webapp!


webapp は次の3つのパートからなります。



  • リクエストの処理と、レスポンスを生成する、RequestHandler クラス

  • URL に従ってリクエストをルーティングする、WSGIApplication インスタンス

  • WSGIApplication を起動する、メインルーチン


さっそく、helloworld.py を書き換えてみます。


helloworld/helloworld.py
from google.appengine.ext import webapp
from google.appengine.ext.webapp.util import run_wsgi_app

class MainPage(webapp.RequestHandler):
def get(self):
self.response.headers['Content-Type'] = 'text/plain'
self.response.out.write('Hello, webapp World!')

application = webapp.WSGIApplication(
[('/', MainPage)],
debug=True)

def main():
run_wsgi_app(application)

if __name__ == "__main__":
main()


開発サーバを起動し、http://localhost:8080 にアクセスします。

ブラウザに Hello, webapp World! が表示されれば成功です!




参考:


2009/01/22

[GAE]Google App Engine を使ってみる Hello, World!


昨年の4月にリリースされた、Google App Engine ですが、アカウントを申請したままずっと放置していました。


「一年の計は元旦にあり」ということで、今後ますますクラウド化していく世の中に備え、Google App Engine、Amazon Web Services は押さえておかなければなりません。


そこで、まずは Google App Engine を使ってみました。



使い始めるのは簡単で、



  1. Python のインストール

  2. Google App Engine SDK のインストール


これですぐにローカルで開発が始められます。


Hello, World!


なにはともあれ、まずは Hello, World! ですね。


シンプルな Request Handler を作成

helloworld ディレクトリを作成し、その中に helloworld.py を作成します。


helloworld/helloworld.py
print 'Content-Type: text/plain'
print ''
print 'Hello, World!'

設定ファイルを作成

helloworld ディレクトリに、app.yml を作成します。


helloworld/app.yml
application: helloworld
version: 1
runtime: python
api_version: 1

handlers:
- url: /.*
script: helloworld.py

アプリケーションのテスト

dev_appserver.py を実行し、ウェブサーバを起動します。実行するアプリケーションディレクトリのパスを渡します。


google_appengine/dev_appserver.py helloworld/

開発サーバが起動したら、http://localhost:8080 にアクセスします。

ブラウザに Hello, World! が表示されれば成功です!



参考:


2008/12/24

Google Android Dev Phone 1

Android

弊社CEOから、クリスマスプレゼントということで、開発者向けテスト機として Google から販売されている、Android Dev Phone 1 を渡されました。


Android

使い始めるにあたり、次の事項に注意しました。



  1. アクティベーションに、データ接続が必要なので、あらかじめ Mopera U (docomo) を契約

  2. アクティベーション完了後に、Gmail 同期が行われるので、受信トレイをアーカイブしておく


SIMカードは、現在使用中のFOMA携帯用のものでOKでした。


気になるパケット代は、受信トレイをアーカイブしておいたので、mopera U の基本料金300円 + 200円程度のパケット料で済んだようです。


さっそく使ってみると、肝心の WiFi が認識しません!

あれこれ試して、結局、無線LANのチャネルの設定を変更して認識。接続成功です。


使用感は、iPhone に負けないくらいすばらしいですね。

まさに、PC と Mac といった感じでしょうか(良くも悪くも)。

iPhone のマルチタッチは使いやすいですが、Android のシングルタッチ + トラックボールもかなり使いやすいですね。



というわけで、日本で Android 携帯が発売される前には、なにかアプリを作ってみたいですね。

2008/06/12

Google Developer Day 2008

今年もGoogle Developer Dayに参加してきました。


Google Developer Day 2008 GDD2008


基調講演では、Client / Connectivity / Cloud という「3つのC」が次世代Webのキーワードで、Googleではそれぞれ、Gears / Android / Google App Engine を提供しているというお話でした。


Google Developer Day 2008 GDD2008
Google Developer Day 2008 GDD2008
Google Developer Day 2008 GDD2008

どれもまだ触っていないので、近いうちに弄ってみたいと思います。


午後のセッションでは、今回はコードラボ(その場で実際にコーティングしながらアプリを作る)という新しいタイプのセッションがあったので、最近流行りのOpenSocialのコードラボに参加しました。


Google Developer Day 2008 GDD2008

どうせなら前回とはちょっと違ったものにと軽い気持ちだったのですが、OpenSocialをどのように利用するのかいまいちピンと来ていなかった自分には、他のメンバーとディスカッションすることができて、非常に参考になりました。


作成するアプリのアイディアは次のようなもの



  • 自分と友達で動物占い

  • 友達にタグ付

  • チャット

  • フィードリーダー

  • 地図から友達検索

  • 足跡


いくつかのグループに分かれてアプリを作成するのですが、上記の中でも一番ソーシャルっぽい占いアプリに参加


短い時間で、適当に作業担当を決めて作成していくのですが、みんなでワイワイやるといった感覚は普段はまったくないので、非常に新鮮な体験でした。作り終えたときの感動は格別です。


予習の際、デバッグにひどく苦労したのですが、コードラボではGoogleの方に教えて頂いた、CodeRunnerというアプリのおかげで、すぐさまTry and Errorができ非常に助かりました。


それと、いろいろなAPIが提供されているOpenSocialですが、やはり(APIだけでは)難しいところは、サーバ側で処理するのが常套手段とのことでした。


Google Developer Day 2008 GDD2008

前回とはまた違った刺激を受けることができました。是非また参加したいと思いました。

2008/05/09

Gmail 2.0 で複数の署名を切り替える

GmailGmail Template Switch

僕は普段Gmailを使っていて、Greasemonkeyスクリプトの「Gmail Template Switch」を愛用しています。


複数のアカウントで使用したり、差出人によって署名・挨拶文を変えたい場合に非常に便利なGreasemonkeyスクリプトです。


Gmail Template Switch

大抵皆そうなんだろうけど、メールを書く場合決まった形があって、あいさつ文・本文・締め言葉・署名という順番で書いている。以前作ったスクリプト で、署名は差出人に応じて自動的に切り替わるようになったけど、あいさつ文や締め言葉は辞書に登録したりして、毎回入力してたわけです。会社で使っていることもあり、社内と社外で定型文が変わってくるので、辞書に登録した語句を忘れたりしてかなり不便だった。これはさすがに面倒なので、さらに Gmail を快適にすべく、テンプレートを切り替えられる Greasemonkey スクリプトを作ってみた。これで署名が複数あっても、定型文が複数あっても平気ですな。


Gmail にテンプレート切り替え機能を付けてみた - 記憶は削除の方向で



しかしながら、Gmail Template Switch は、Gmail 2.0では動作しないため、動作が快速でラベルの色分け等便利な機能は我慢し、日本語版Gmailを使っていたわけですが、先日ついに、日本語版Gmailも 2.0になってしまいました。


というわけで、Gmail Template Switch を、Gmail 2.0 で動作するようにハックしてみました。


Gmail Template Switcher - v 2.0


既存のデータは生かしたかったので、コードはほとんど流用しています。なお、jQueryの使用で意味不明なエラーが出たので、使わないようにハックしてます。

また、Gmail 2.0は、Greasemonkeyを正式にサポートするらしく、GmailGreasemonkey10APIが公開されているので、せっかくなので使ってみました。


はじめは軽い気持ちでやってみたのですが、Greasemonkeyは初めて、かつ、Gmail 2.0 になってずいぶん変わっているようで、とても苦戦しました・・・(汗


とくに、ElementのIDがランダムで変わるのと、Reply時の入力フォームが動的に挿入される点でかなりはまりました・・。


なお、操作方法等はオリジナルとかわりません。もちろん新しい機能なんてありませんw


以下、コード全文です。


※最新のコードはこちら


Update:

Safari (Greasekit), Google Chrome, Opera で動作を確認しました。

Update2:

Google Chrome Extension 版を作成しました。


gmailtemplateswitcherv20.user.js
// ==UserScript==
// @name Gmail Template Switcher - v 2.0
// @namespace http://www.r-stone.net/blogs/ishikawa
// @description Append the function to apply the mail template, when writing a mail. Modify source code of [Gmail Template Switch] from http://d.hatena.ne.jp/re_guzy by re_guzy
// @version 0.1.20080510.0
// @include http://mail.google.com/*
// @include https://mail.google.com/*
// @exclude http://mail.google.com/mail/help/*
// @exclude https://mail.google.com/mail/help/*
//
// Copyright (c) 2007-2008, re_guzy <goodspeed.xii@gmail.com>
// Distributed under the MIT license
// http://opensource.org/licenses/mit-license.php
// http://sourceforge.jp/projects/opensource/wiki/licenses%2FMIT_license
//
// Notice : To Uninstall this script, remove "gtssettings0-9" from Gmail contact list.
// Feature: When writing a mail, append a combobox to action. By selecting action,
// apply template to mail, or add template or remove template. Template
// is saved to "contact list" named starting with "gtssettings".
// Require: Greasemonkey 0.7.20080121.0
// ==/UserScript==

const DEBUG = false;
const KEY_TOKEN = "gts_token";
const KEY_CACHE = "gts_cache";
const CONTACT_NAME = "gtssettings";
const CONTACT_ID_RE = /\["\w+","(\w+)","gtssettings\d","gtssettings\d",/;
const MSGBODY_RE = /([\s\S]*)\n?(?:^---)(\n[\s\S]+)/m;
const LOCATION_RE = /(https?:\/\/[^\/]+\/(a\/[^\/]+\/)?).*/;
const SELECTOR = {
'gts' : 'descendant::*[local-name() = "select" or local-name() = "SELECT"][@id = "id_gts_template"]',
'input_form' : 'descendant::*[local-name() = "form" or local-name() = "FORM"]',
'body' : 'descendant::*[local-name() = "textarea" or local-name() = "TEXTAREA"][@name = "body"]',
'subject' : 'descendant::*[local-name() = "input" or local-name() = "INPUT"][@name = "subject"]',
'to' : 'descendant::*[local-name() = "textarea" or local-name() = "TEXTAREA"][@name = "to"]',
'from' : 'descendant::*[local-name() = "select" or local-name() = "SELECT"][@name = "from"] | descendant::*[local-name() = "input" or local-name() = "INPUT"][@name = "from"]',
'cc' : 'descendant::*[local-name() = "textarea" or local-name() = "TEXTAREA"][@name = "cc"]',
'bcc' : 'descendant::*[local-name() = "textarea" or local-name() = "TEXTAREA"][@name = "bcc"]',
'gts_undo' : 'descendant::*[contains(concat(" ",@class," "), " gts_undo_option ")]',
'gts_first' : 'descendant::*[contains(concat(" ",@class," "), " gts_option_first ")]',
'discard' : 'descendant::*[local-name() = "button" or local-name() = "BUTTON"][count(preceding-sibling::*[local-name() = "button" or local-name() = "BUTTON"]) = 2]',
'labels' : 'descendant::*[local-name() = "span" or local-name() = "SPAN"]',
'msg' : 'div/div/div/div/div/div/div/div/div/div/div[3]/div/div[2]/div[2]/div/div[2]/div[2]/div/table/tbody/tr[2]/td[2]'
}

var T = new Array(10);
T[0] = { 'id' : -1, 'num' : 0 };
for (var i=1;i < T.length;i++) {
T[i] = {
'id' : -1,
'num' : i,
'from' : '',
'to' : '',
'cc' : '',
'bcc' : '',
'subject' : '',
'body' : '',
'body_latter' : ''
}
}
var Ja = false;
var recentView;

//Initialize gmail and gmonkey objects
window.addEventListener('load', function() {
if (unsafeWindow.gmonkey) {
unsafeWindow.gmonkey.load('1.0', function(gmail) {

function getViewType() {
var str = '';
switch (gmail.getActiveViewType()) {
case 'tl': str = 'Threadlist'; break;
case 'cv': str = 'Conversation'; break;
case 'co': str = 'Compose'; break;
case 'ct': str = 'Contacts'; break;
case 's': str = 'Settings'; break;
default: str = 'Unknown';
}
return str;
}

function getView() {
return gmail.getActiveViewElement();
}

function getMailForm() {
var a = xpath(SELECTOR['input_form'], getView());
if (a && a.length > 0) {
return a[0];
} else {
return null;
}
}

function getGts() {
var a = xpath(SELECTOR['gts'], getView());
if (a && a.length > 0) {
return a[0];
} else {
return null;
}
}

function getGtsOptUndos() {
return a = xpath(SELECTOR['gts_undo'], getGts());
}

function getGtsOptFirst() {
var a = xpath(SELECTOR['gts_first'], getGts());
if (a && a.length > 0) {
return a[0];
} else {
return null;
}
}

function getFrom(form) {
var a = xpath(SELECTOR['from'], form || getMailForm());
if (a && a.length > 0) {
return a[0];
} else {
return null;
}
}

function getTo(form) {
var a = xpath(SELECTOR['to'], form || getMailForm());
if (a && a.length > 0) {
return a[0];
} else {
return null;
}
}

function getCc(form) {
var a = xpath(SELECTOR['cc'], form || getMailForm());
if (a && a.length > 0) {
return a[0];
} else {
return null;
}
}

function getBcc(form) {
var a = xpath(SELECTOR['bcc'], form || getMailForm());
if (a && a.length > 0) {
return a[0];
} else {
return null;
}
}

function getSubject(form) {
var a = xpath(SELECTOR['subject'], form || getMailForm());
if (a && a.length > 0) {
return a[0];
} else {
return null;
}
}

function getBody(form) {
var a = xpath(SELECTOR['body'], form || getMailForm());
if (a && a.length > 0) {
return a[0];
} else {
return null;
}
}

function getDiscard() {
var a = xpath(SELECTOR['discard'], getMailForm().parentNode.parentNode);
if (a && a.length > 0) {
return a[0];
} else {
return null;
}
}

function getLabels(form) {
return a = xpath(SELECTOR['labels'], form || getMailForm());
}

function getCcLabel() {
var a = getLabels();
for (var i=0; i<a.length; i++) {
if (/Cc/.exec(a[i].innerHTML)) {
return a[i];
}
if (i>1) {
break;
}
}
return null;
}

function getBccLabel() {
var a = getLabels();
for (var i=0; i<a.length; i++) {
if (/Bcc/.exec(a[i].innerHTML)) {
return a[i];
}
if (i>1) {
break;
}
}
return null;
}

function getChangeLabel() {
var a = getLabels();
for (var i=0; i<a.length; i++) {
if (/change/.exec(a[i].innerHTML) || /変更/.exec(a[i].innerHTML)) {
return a[i];
}
if (i>1) {
break;
}
}
return null;
}

function switcher() {
str = getViewType();

if (str != "Compose" && str != "Conversation") {
if (recentView) {
recentView.removeEventListener('DOMNodeInserted', nodeInsertedHandler, false);
}
return;
}
recentView = getView();
window.setTimeout(function() {
initialize();
}, 600);
}

function nodeInsertedHandler(event) {
target = event.target;
if (target.nodeType == 1) {
tagName = target.tagName.toLowerCase();
if (tagName == 'form') {
log('form inserted');
window.setTimeout(function() {
initialize();
}, 500);
//getView().removeEventListener('DOMNodeInserted', nodeInsertedHandler, false);
} else if (tagName == 'table') {
log('table inserted');
window.setTimeout(function() {
initialize();
}, 500);
//getView().removeEventListener('DOMNodeInserted', nodeInsertedHandler, false);
}
}
}

function initialize() {
log('initialize');

try {

if (getGts()) {
log('already initialized');
return;
}

var form = getMailForm();
if (!form) {
log('form not found');
getView().addEventListener('DOMNodeInserted', nodeInsertedHandler, false);
return;
}
//getView().removeEventListener('DOMNodeInserted', nodeInsertedHandler, false);

var discard_button = getDiscard();
var label = discard_button.innerHTML;
Ja = (label == '破棄');
discard_button.parentNode.insertBefore(createSelectElement(), discard_button.nextSibling);
composeCommand(form);
} catch(e) {
log('add combobox failure because : ' + e);
return;
}
}

function createSelectElement() {
var content = document.createElement('select');
content.setAttribute("id", "id_gts_template");
content.setAttribute("style", "margin-left:10px;font-size:.8em;");
content.innerHTML = toOption('please wait...' , false , true);
content.addEventListener('change', function(event) {doCommand(event.target)}, true);
return content
}

function toOption(text, value, selected, cls, isDom) {
if (isDom) {
var attr = {
'style' : value ? null : 'color: rgb(119, 119, 119);',
'disabled' : value ? null : 'disabled',
'selected' : selected ? 'selected' : null,
'value' : value ? value : null,
'class' : cls ? cls : null
};
var elm = document.createElement('option');
for (var i in attr) {
if (attr[i]) {
elm.setAttribute(i, attr[i]);
}
}
elm.innerHTML = text;
return elm;
} else {
var attr = {
'style' : value ? null : '"color: rgb(119, 119, 119);"',
'disabled' : value ? null : '"disabled"',
'selected' : selected ? '"selected"' : null,
'value' : value ? '"' + value + '"' : null,
'class' : cls ? cls : null
};
var a = [];
for (var i in attr) {
if (attr[i]) {
a.push(i + "=" + attr[i]);
}
}
return "<option " + a.join(' ') + ">" + text + "</option>";
}
}

function composeCommand(form) {
getTemplates(function /*parseTemplate*/(notes, use_cache) {
for (var i in notes) {
if (use_cache) {
T[i] = notes[i];
} else {
var note = notes[i].note ? decode(notes[i].note, false) : "{}";
try {
T[notes[i].num] = eval(note);
T[notes[i].num].id = notes[i].id;
T[notes[i].num] = decode(T[notes[i].num], true);
} catch(e) {log("eval failed : " + e);}
}
}

recomposeSelectElement(form);
applyDefault(form);
if (notes.length == 0) {
save();
} else if (!use_cache) {
var caches = [];
for (var i in T) {
var encoded = encode(T[i], true);
if (encoded) {
caches.push(encoded.toSource());
}
}
GM_setValue(KEY_CACHE, "[" + caches.join(", ") + "]");
}
});
}

function applyDefault(form) {
var from = getFrom();
if (from) {
var fromvalue = from.value;
matched = grep(T, function(i) {
return (i.name + '').indexOf('#') == 0 && i.from == fromvalue;
});
if (matched.length > 0) {
applyTemplate(matched[0].num, form);
}
}
}

function recomposeSelectElement(form) {
var options = [];
options.push(toOption(trans("Template actions...") , "init" , true, "gts_option_first"));

var enables = grep(T, function(o) {return (o.name && o.num != 0);});
var expand = function(arrays, cmd) {
var hash = {};
for (var i in enables) {
hash[enables[i].from] = hash[enables[i].from] || [];
hash[enables[i].from].push(enables[i]);
}
for (var i in hash) {
arrays.push(toOption(' <' + (i || trans('No from')) + '>'));
for (var j in hash[i]) {
arrays.push(toOption('  ' + hash[i][j].name , cmd + '_' + hash[i][j].num));
}
}
};

var tmp = [
{'cmd':'apply', 'exp':trans("Apply"), 'func':expand},
{'cmd':'add','exp':trans("Append"), 'func':function(arrays, cmd) {
var used = grep(T , function(o) { return (o.name && o.num != 0) });
if (used.length < 9) {
arrays.push(toOption('  ' + trans("Includes from") , cmd));
arrays.push(toOption('  ' + trans("Excludes from") , cmd + '_ignore_from'));
} else {
arrays.push(toOption('  ' + trans('Quantity limit is 9')));
}
}},
{'cmd':'delete','exp':trans('Remove'), 'func':expand}
];
for (var i in tmp) {
if (tmp[i].func != expand || enables.length != 0) {
options.push(toOption('-------'));
options.push(toOption(trans('verbs', tmp[i].exp) + ':'));
tmp[i].func(options, tmp[i].cmd);
}
}

var gts = getGts();
gts.innerHTML = options.join('');
gts.value = 'init';
}

function doCommand(selectNode) {
var form = getMailForm();
if (form) {
if (selectNode.value == 'add') {
addTemplate(form, true);
} else if (selectNode.value == 'add_ignore_from') {
addTemplate(form, false);
} else if (selectNode.value.match(/apply_(\d+)/)) {
applyTemplate(RegExp.$1 , form);
} else if (selectNode.value.match(/delete_(\d+)/)) {
deleteTemplate(RegExp.$1 , form);
} else if (selectNode.value == 'undo') {
if (unsafeWindow.gts_undo) { unsafeWindow.gts_undo(); }
}
selectNode.value= 'init';
} else {
log('form not found');
}
}

function addTemplate(form, contain_from) {
var m = trans('Please input the template name.');
if (contain_from) {
m += '\n';
m += trans('If the name is started from "#", it becomes default of the corresponding "from".');
}
var name = window.prompt(m, "");
if (!name) {return;}
if (grep(T , function(o) { return (o.name == name) }).length > 0) {
alert(trans('The name already exists.'));
return;
}

var empties = grep(T , function(o) { return (o.name || o.num == 0) }, true);
var t = empties[0];

var to = getTo(form);
var b = getBody(form);
var c = getCc(form);
var bc = getBcc(form);
var f = getFrom(form);
var s = getSubject(form);
T[t.num] = {
'num' : t.num,
'id' : t.id,
'name' : name,
'from' : contain_from ? f.options[f.selectedIndex].value : "",
'to' : to.innerHTML,
'cc' : c.innerHTML,
'bcc' : bc.innerHTML,
'subject' : s.value,
'body' : b.innerHTML
};

if (MSGBODY_RE.exec(T[t.num].body)) {
T[t.num].body = RegExp.$1;
T[t.num].body_latter = RegExp.$2;
}

editContact(T[t.num], function() {
recomposeSelectElement(form);
msg(trans('appended', name));
});
}

function applyTemplate(num , form) {
if (typeof T[0]._init == 'undefined') {
T[0]._init = {};
var selectors = x('f,s,t,c,bc,bo');
for (var i in selectors) {
var j = xpath(selectors[i], form)[0];
T[0]._init[j.name] = trim(j.value);
}
}

var selectors = x('f,s,t,c,bc');
for (var i in selectors) {
var j = xpath(selectors[i], form)[0];
var tmp = T[0]._init[j.name];
if (j.name != 'from' || j.value != T[num][j.name]) {
if (j.type == 'textarea') {
var targetaddrs = tmp || "";
//既に含まれているものは追加しない
var notcontains = grep(T[num][j.name].split(','), function(i) {
if (trim(i).length == 0) { return; }//空白は無視
if (/([\w\.+-]+@[\w+-]+(\.[\w+-]+)+)/.exec(i)) {
return targetaddrs.indexOf(RegExp.$1) < 0;
} else {
return targetaddrs.indexOf(i) < 0;
}
});
if (tmp) { notcontains.unshift(tmp); }
j.value = notcontains.join(', ');
} else {
j.value = (j.name == 'subject' && tmp) ? tmp : T[num][j.name];
}
if (j.name == 'cc' && T[num][j.name]) {
var cc_label = getCcLabel();
if (cc_label) {
emulate_click(cc_label);
}
}
if (j.name == 'bcc' && T[num][j.name]) {
var bcc_label = getBccLabel();
if (bcc_label) {
emulate_click(bcc_label);
}
}
}
}

var change_label = getChangeLabel();
if (change_label) {
emulate_click(change_label);
}

var b = getBody(form);
b.value = grep([
T[num].body, T[0]._init.body, T[num].body_latter
], function(i) { return i; }).join("\n\n");

var selectors = x('bo,s,t');
for (var i in selectors) {
var j = xpath(selectors[i], form)[0];
if ((j.name == 'body') || !j.value) {
j.focus();
j.selectionStart = 0;
j.selectionEnd = 0;
}
}

var undos = [
toOption('-------', null, null, "gts_undo_option", true),
toOption('  ' + trans("Undo"), 'undo', null, "gts_undo_option", true)
];
var gts_undos = getGtsOptUndos();
for (var i=0; i<gts_undos.length; i++) {
gts_undos[i].parentNode.removeChild(gts_undos[i]);
}
var gts_first = getGtsOptFirst();
if (gts_first) {
gts_first.parentNode.insertBefore( undos[1], gts_first.nextSibling );
gts_first.parentNode.insertBefore( undos[0], gts_first.nextSibling );
}

msg(trans('applied', T[num].name), function() {
undo(form);
msg(trans("To apply template was canceled."));
}, true);
}

function undo(form) {
if (typeof T[0]._init != 'undefined') {
var selectors = x('f,t,c,bc,bo');
for (var i in selectors) {
var j = xpath(selectors[i], form)[0];
j.value = T[0]._init[j.name];
}
}
delete T[0]._init;
var gts_undos = getGtsOptUndos();
for (var i=0; i<gts_undos.length; i++) {
gts_undos[i].parentNode.removeChild(gts_undos[i]);
}
}

function emulate_click(target) {
if (target.dispatchEvent) {
var e = unsafeWindow.document.createEvent("MouseEvents");
e.initEvent("click", true, true);
target.dispatchEvent(e);
}
}

function deleteTemplate(num , form) {
var name = T[num].name;
if (confirm(trans('remove confirm', name)) != true) {
return;
}

T[num] = {'id' : T[num].id, 'num' : num};
editContact(T[num], function() {
recomposeSelectElement(form);
msg(trans("removed", name));
});
}

function getTemplates(f_parseTemplate) {
var queryUrl = 'mail/contacts/data/contacts?thumb=false&groups=false&show=ALL&psort=Name&max=300&out=js&rf=&jsx=true';
ajax(queryUrl, function(req){
contactPage = req.responseText.replace('while (true); ', '').replace(/&&&START&&&([^&&&]+)&&&END&&&/, "$1");
response = eval("(" + contactPage + ")");
if (response.Success) {
var contacts = response.Body.Contacts;
var notes = [];
for(i=0; i<contacts.length; i++) {
if (contacts[i].Name && /gtssettings(\d)/.exec(contacts[i].Name) ) {
num = RegExp.$1;
note = contacts[i].Notes;
id = contacts[i].Id;
notes.push({'num' : num, 'note' : note, 'id' : id});
}
}
var authtoken = response.Body.AuthToken.Value;
GM_setValue(KEY_TOKEN, authtoken);
f_parseTemplate(notes);
} else {
log("Contacts Request Failed: " + response.Errors[0].Text);
}
});
}

function encode(tmpl, by_escape) {
if (by_escape) {
var escaped = {};
//encodeURIだと「'」がエンコードされないので、escapeを使う
for (var i in tmpl) {
if (i.indexOf('_') != 0) {
escaped[i] = escape(tmpl[i]);
}
}
return escaped;
} else {
//連絡先は「"」で囲まれるため、JSONデータを表すのに「"」を使えない。
//連絡先から復元するときに使うデータを、REGEXでマッチさせるため「"」を「'」に置換しておく。
return tmpl.toSource().replace(/\"/g, "'");
}
}

function decode(tmpl, by_unescape) {
if (by_unescape) {
var unescaped = {};
for (var i in tmpl) {
unescaped[i] = unescape(tmpl[i]);
}
return unescaped;
} else {
//連絡先に格納するために、「'」に変換しておいた「"」を戻す
return tmpl.replace(/\\'/g, "\"");
}
}

function editContact(tmpl, f_completed) {
var authtoken = GM_getValue(KEY_TOKEN);
if (!authtoken) {
log('token not found');
return;
}
var escaped = encode(tmpl, true);
var post_data = param({
"token" : authtoken,
"tok" : authtoken,
"out" : "js",
"id" : tmpl.id,
"action" : "SET",
"Name" : CONTACT_NAME + tmpl.num,
"Emails.0.Address" : CONTACT_NAME + tmpl.num + "@gmail.com",
"Notes" : encode(escaped, false)
});
ajax("mail/contacts/update/contact", function(req) {
var response = eval("(" + req.responseText.replace('while (true); ', '').replace(/&&&START&&&([^&&&]+)&&&END&&&/, "$1") + ")");
if (response.Success) {
if (tmpl.id == -1) {
if (CONTACT_ID_RE.exec(req.responseText)) {
tmpl.id = RegExp.$1;
editContact(tmpl, f_completed);
}
} else {
if (tmpl.num != 0) {
save(f_completed);
} else {
f_completed();
}
}
} else {
log("Update Contact Request Failed: " + response.Errors[0].Text);
}
}, 'POST', {'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8'}, post_data);

}

function log(message) {
if (unsafeWindow && unsafeWindow.console && DEBUG) {
unsafeWindow.console.log(message);
}
}

function getCookie(name) {
var re = new RegExp(name + "=([^;]+)");
var value = re.exec(document.cookie);
return (value != null) ? decodeURI(value[1]) : null;
}

function msg(message, f_clicked, is_undo) {
unsafeWindow.gts_undo = f_clicked;
var a = xpath(SELECTOR['msg'], getView().ownerDocument.body);
if (a && a.length > 0) {
var td = a[0];
var div = td.parentNode.parentNode.parentNode.parentNode;
div.style.visibility = "visible";
td.innerHTML = "GTS : " + message;
if (is_undo) {
var span = document.createElement('span');
span.setAttribute("id", "gts_und");
span.setAttribute("class", "lk");
span.innerHTML = trans('Undo link');
span.addEventListener('click', f_clicked, true);
td.appendChild(span);
}
window.setTimeout(function() {
div.style.visibility = "hidden";
}, 60000);
}
}

function save(f_saved) {
T[0]['num'] = 0;
T[0].date = new Date();
editContact(T[0], f_saved ? f_saved : function() {});
}

function ajax(request_path, f_load, get_or_post, headers, data) {
window.setTimeout(function() {
GM_xmlhttpRequest({
'method': get_or_post ? get_or_post : "GET",
'url': getBaseLocation() + request_path,
'data': data,
'headers': headers,
'onload': f_load,
'onerror': function(req) {
log("Request Failed in error code: " + req.status);
}
});
}, 0);
}

function getBaseLocation() {
if (LOCATION_RE.exec(document.location)) {//for Google Apps
return RegExp.$1;
} else {
return 'http://mail.google.com/';
}
}

function trans(msg_id, opt) {
return {
'Template actions...' : Ja ? 'テンプレートの操作...' : msg_id,
'Apply' : Ja ? '適用' : msg_id,
'Append' : Ja ? '追加' : msg_id,
'Includes from' : Ja ? '差出人を含む' : msg_id,
'Excludes from' : Ja ? '差出人を除く' : msg_id,
'Quantity limit is 9' : Ja ? '最大9個です' : msg_id,
'Remove' : Ja ? '削除' : msg_id,
'verbs' : Ja ? (opt + 'するテンプレート') : (opt + ' template'),
'Please input the template name.' : Ja ? 'テンプレート名を入力してください。' : msg_id,
'If the name is started from "#", it becomes default of the corresponding "from".' :
Ja ? '名前を「#」から始めると、対応する差出人のデフォルトになります。' : msg_id,
'The name already exists.' : Ja ? 'その名前は既に存在します。' : msg_id,
'appended' : Ja ? ("テンプレート「" + opt + "」を追加しました。")
: ('Template "' + opt + '" was appended.'),
'applied' : Ja ? ("テンプレート「"+opt+"」を適用しました。")
: ('Template "' + opt + '" was applied. '),
'remove confirm' : Ja ? ("テンプレート「" + opt + "」を削除しますか?")
: ('Is template "' + opt + '" removed?'),
'removed' : Ja ? ("テンプレート「" + opt + "」を削除しました。")
: ('Template "' + opt + '" was removed.'),
'To apply template was canceled.' : Ja ? "テンプレートの適用は取り消されました。" : msg_id,
'Undo' : Ja ? "適用の取り消し" : msg_id,
'Undo link' : Ja ? "適用取り消し" : "Undo applied",
'No from' : Ja ? "差出人なし" : msg_id
}[msg_id] || msg_id;
}

function x(prefix) {
var result = [];

for (var i in SELECTOR) {
if (typeof prefix != 'undefined') {
var a = grep(prefix.split(','), function(j) { return i.indexOf(j) == 0; });
if (a.length != 0) { result.push(SELECTOR[i]); }
} else {
result.push(SELECTOR[i]);
}
}
return result;
}

/*
* this 'grep' function from jquery-1.2.2.js
* jQuery 1.2.2 - New Wave Javascript
*/
function grep( elems, callback, inv ) {
// If a string is passed in for the function, make a function
// for it (a handy shortcut)
if ( typeof callback == "string" )
callback = eval("false||function(a,i){return " + callback + "}");

var ret = [];

// Go through the array, only saving the items
// that pass the validator function
for ( var i = 0, length = elems.length; i < length; i++ )
if ( !inv && callback( elems[ i ], i ) || inv && !callback( elems[ i ], i ) )
ret.push( elems[ i ] );

return ret;
}

/*
* this 'param' function from jquery-1.2.2.js
* jQuery 1.2.2 - New Wave Javascript
*/
function param(a) {
var s = [];

// Serialize the key/values
for ( var j in a )
// If the value is an array then the key names need to be repeated
s.push( encodeURIComponent(j) + "=" + encodeURIComponent( a[j] ) );

// Return the resulting serialization
return s.join("&").replace(/%20/g, "+");
}

/*
* this 'trim' function from jquery-1.2.2.js
* jQuery 1.2.2 - New Wave Javascript
*/
function trim(text) {
return (text || "").replace( /^\s+|\s+$/g, "" );
}

function xpath(query, target) {
var results = document.evaluate(query, target || document, null,
XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
var nodes = [];
for (var i=0; i<results.snapshotLength; i++) {
nodes.push(results.snapshotItem(i));
}
return nodes;
}

gmail.registerViewChangeCallback(switcher);
switcher();
});
}
}, true);

2008/03/09

RubyでGoogle Data APIs

Googleが提供するさまざまなサービスには、Google Data APIsを利用して、データの取得や更新を行うことができます。


現在、以下のサービスへのAPIが提供されています。



JavaScriptからSpreadsheetsを操作できないかと調べてみたのですが、JavaScript client libraryは、現在、Google Calendar と Bloggerのみのようです。


そこで、Rubyのライブラリを探してみると、gdata-rubyというライブラリがありました。
ただ、このライブラリは現在開発がストップしているらしく、SpreadsheetとBloggerのみに対応しているようです。


というわけで、せっかくなのでRubyでGoogle Spreadsheets data APIを試してみました。


ruby-gdataのインストール


gem install GData

書き込みテスト


#!/usr/bin/env ruby

require 'gdata/spreadsheet'

gdata_user = 'xxx@gmail.com'
gdata_pass = 'xxx'
gs_key = 'xxx'

gs = GData::Spreadsheet.new(gs_key)
gs.authenticate(gdata_user, gdata_pass)

gs.add_to_cell 'sin(0.2)'

残念ながら、使用感はいまいちでした。他によいライブラリはないのでしょうか・・・?

2008/02/29

Google Chart API を使ってみる

しばらく前に、Google Chart API が公開されたのですが、後れ馳せながら使ってみました。


Google Chart APIは、Google Static Maps APIと同じようにグラフを画像(PNGフォーマット)で返してくれるAPIです。(Google Chart APIが先ですが・・)


Google Chart API - Hello World
http://chart.apis.google.com/chart?cht=p3&chd=s:hW&chs=400x200&chl=Hello|World


Google Chart API のURLは、次の形式になっていて、parameters部分は以下のようになっています。※グラフの種類によって違ってきます。



http://chart.apis.google.com/chart?parameters


chs

グラフのサイズ

e.g. 400x250

※最大値は1000x1000

chd

グラフのデータ

e.g. s:helloWorld

データをエンコードした文字列を設定します

cht

グラフの種類

e.g. lc

chxt

軸ラベルの表示

e.g. x,y

chxl

軸ラベル

e.g. 0:|Mar|Apr|May|June|July|1:||50+Kb


上記の例だと、このようなグラフになります。



http://chart.apis.google.com/chart?chs=400x250&chd=s:helloWorld&cht=lc&chxt=x,y&chxl=0:|Mar|Apr|May|June|July|1:||50+Kb


表示できるグラフの種類は、円・棒・折れ線グラフはもちろん、面グラフや散布図といったものまで表示でき、大概のグラフは表示できそうです。


これまでグラフを貼り付けるのに、Excelで作成したものを貼り付けたり、Flashでやっていたものが、簡単に貼り付けられるようになったのは素晴らしいと思います。ただ、今のところ(2008/02/29現在)、ラベルに日本語はうまく表示できないようなのがちょっと残念。


なお、利用にあたっては、ユーザあたり50,000リクエスト/日(query limit of 50,000 queries per user per day)という制限があります。

2008/02/28

Google Static Maps APIを使ってみる

先日、より簡単にGoogleマップを表示できるようになる、Google Static Maps API公開されました。


特定の地図をimageデータ(GIFフォーマット)として返してくれるようで、いろいろと使い道があるのではないでしょうか。(特にモバイル)


というわけで、さっそく使ってみました。


River Stone Inc.


携帯で表示(240x270)
QRコード
http://www.r-stone.net/blogs/ishikawa/google_static_maps_api_mobile.html


使い方は非常に簡単で、Static Map Wizardで手軽に作成できます。


Google Static Maps API のURLは、次の形式になっていて、parameters部分は以下のようになっています。



http://maps.google.com/staticmap?parameters


center(必須)

マップの中央の座標

e.g. 40.714728,-73.998672

zoom(必須)

ズームレベル

e.g. 17

size(必須)

画像のサイズ

e.g. 500x400

※最大値は512x512

maptype(オプション)

roadmap(デフォルト):通常のマップ

mobile:表示文字等が簡略化されたモバイル用

markers(オプション)

マーカーの位置,色,文字

※"|"で区切ることで複数マーカーが可能

{latitude},{longitude},{color}{alpha-character}

e.g. 40.702147,-74.015794,blues|40.711614,-74.012318,greeng

key(必須)

APIキー

Google Maps APIと同じ


なお、利用にあたって、1ユーザ(1IPアドレス)当たり、表示は1日1,000種類の画像までという制限(※原文によると「1000 unique (different) image requests per viewer per day」)があるので要注意です。

2008/01/25

Google ブックマークのデータが消えている

どこでも共通のブックマークが使えるように、ブックマークにはGoogle ブックマークを使っているのですが、今朝(2008/01/25現在)アクセスしてみると、いくつかのブックマークを除き、ほとんどが消えてしまっています。

はじめは、自分の誤操作かとも疑いましたが、ここ最近のものを除いたすべてが消えているようです。

これはおかしいと思い調べてみると、同様の現象に見舞われている人がいるようです。


Google ツールバーのブックマークが消えた -OKWave


昨晩まで使えていた Google ツールバー のブックマークが今朝、ただひとつのブックマーク(直近に追加したもの)だけを残して消えていました(同じツールバーの検索タイプの方は登録内容が残っています)。

ツールバーの[ブックマーク]−[更新]を何度か試みましたが復活しません。登録内容はエクスポートしていません。元に戻す方法ないでしょうか?




上記サイトにあるように、僕の場合もGoogle Notebookにデータ自体あったのですが、手動での復旧は億劫なので、Googleの対応を待ったほうがよさそうです・・・。



データがどんどんネットに保存されて便利になっていく一方で、それが壊れたときの衝撃はすさまじいものだと痛感しました。

サービス提供者として、肝に銘じておかなければなりません。



Update:

既に復活しているようです。夕方くらいには復活したようですね。