2010年10月26日火曜日

タッチイベントはMapViewとItemizedOverlayのどちらにまず伝わるのか

Android+Google MAP APIでタッチイベントを処理する時のイベントの流れを確認する。
使ったのは以下のクラス。

・MapDemoActivityクラス
MapActivityを継承したクラス。実行クラス。

・CusomizedMapViewクラス
MapViewを継承したクラス。MapDemoActivityのContentViewとして設定。

・GeoItemizedOverlayクラス
ItemizedOverlayを継承したクラス。


これらのクラスは、下から積み上げると以下のような階層関係にある。

GeoItemizedOverlay
|
CusomizedMapView
|
MapDemoActivity


・検証

ディバイスの画面にタッチしたときには、onTouchEvent(MotionEvent event)が呼び出される。

MapViewを使ったときは、Activityに記述したonTouchEventではなくMapView側のonTouchEventが呼び出されることは、前の記事に書いた。

では、MapViewにItemizedOverlayが追加されているときは、タッチイベントはMapViewとItemizedOverlayのどちらにまず伝わるのか。

public class MapDemoActivity extends MapActivity {
 @Override
 public void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.main);

  /** マップビュー作成 */
  CustomizedMapView mapView = (CustomizedMapView) findViewById(R.id.mapview);

  Drawable drawable = this.getResources().getDrawable(R.drawable.star);
  GeoItemizedOverlay geoOverlay = new GeoItemizedOverlay(drawable, this);

  /** オーバーレイをマップに追加 */
  List<Overlay> mapOverlays = mapView.getOverlays();
  mapOverlays.add(geoOverlay);
 }
}
public class CustomizedMapView extends MapView {
 @Override
 public boolean onTouchEvent(MotionEvent event) {
  Log.i("onTouchEvent", "CustomizedMapView.onTouchEvent");
  return super.onTouchEvent(event);
 }
}
public class GeoItemizedOverlay extends ItemizedOverlay<OverlayItem> {
 @Override
 public boolean onTouchEvent(MotionEvent event, MapView mapView) {
  Log.i("onTouchEvent", "GeoItemizedOverlay.onTouchEvent");
  return super.onTouchEvent(event, mapView);
 }
}

試してところ、ItemizedOverlayよりも先にMapViewへタッチイベントが伝わることが分かった。
10-26 13:29:55.577: INFO/MapDemo(4253): CustomizedMapView.onTouchEvent
10-26 13:29:55.577: INFO/MapDemo(4253): GeoItemizedOverlay.onTouchEvent

上記のクラスで言うと、

CustomizedMapView→GeoItemizedOverlay

の順番だ。


・結論

MapView側に記述してあるonTouchEventがまず呼ばれる。

そして、MapViewのonTouchEventの最後に実行する、super.onTouchEvent(event)により、MapViewに追加されている各Overlayへタッチイベントが伝えられていく、という流れのようだ。

ItemizedOverlayをMapViewに追加しているときにアイテムが無いとNullPointerExceptionが発生するバグへの対応

ItemizedOverlayがMapViewに追加されているときは、MapViewクラスのonTouchEvent(MotionEvent event)が実行される際に、ItemizedOverlayのonTouchEvent(MotionEvent event)にもイベントが投げられる。

問題は、それがアイテム数が0の時にも起こるので、アイテム数が0の時に画面にタッチするとNullPointerExceptionが発生すること。

10-26 12:00:24.607: ERROR/AndroidRuntime(2753): java.lang.NullPointerException
10-26 12:00:24.607: ERROR/AndroidRuntime(2753):     at com.google.android.maps.ItemizedOverlay.getItemsAtLocation(ItemizedOverlay.java:617)
10-26 12:00:24.607: ERROR/AndroidRuntime(2753):     at com.google.android.maps.ItemizedOverlay.getItemAtLocation(ItemizedOverlay.java:586)
10-26 12:00:24.607: ERROR/AndroidRuntime(2753):     at com.google.android.maps.ItemizedOverlay.handleMotionEvent(ItemizedOverlay.java:498)
10-26 12:00:24.607: ERROR/AndroidRuntime(2753):     at com.google.android.maps.ItemizedOverlay.onTouchEvent(ItemizedOverlay.java:572)
10-26 12:00:24.607: ERROR/AndroidRuntime(2753):     at hoge.MapDemo.GeoItemizedOverlay.onTouchEvent(GeoItemizedOverlay.java:54)
10-26 12:00:24.607: ERROR/AndroidRuntime(2753):     at com.google.android.maps.OverlayBundle.onTouchEvent(OverlayBundle.java:63)
10-26 12:00:24.607: ERROR/AndroidRuntime(2753):     at com.google.android.maps.MapView.onTouchEvent(MapView.java:625)
10-26 12:00:24.607: ERROR/AndroidRuntime(2753):     at hoge.MapDemo.CustomizedMapView.onTouchEvent(CustomizedMapView.java:52)

今の所この現象を避けるには、ItemizedOverlayを継承したクラスのコンストラクタで、super(boundCenterBottom(defaultMarker));の後で、populate()を呼ぶこと。


・ItemizedOverlayを使うときは、必ずコンストラクタ内でpopulate()を呼ぶ

populate()はアイテムを追加した後に必ず呼ぶメソッドだが、アイテムがあろうがなかろうが、最初からコールしておくということ。これでNullPointerExceptionは発生しなくなる。

public class GeoItemizedOverlay extends ItemizedOverlay<OverlayItem> {

 private ArrayList<OverlayItem> mOverlays = new ArrayList<OverlayItem>();
 private Context mContext;

 public GeoItemizedOverlay(Drawable defaultMarker, Context context) {
  super(boundCenterBottom(defaultMarker));
  mContext = context;
  populate();//これが大事!
 }
}

このバグはIssueとして管理されているので、早めに解決して欲しい方はvoteしてください。
Issue 2035 - android - NullPointerException when scrolling through a MapView with an ItemizedOverlay with no OverlayItems - Project Hosting on Google Code

MapView継承クラスでonTouchEvent()を処理するときに気をつけること

MapViewを利用すると、MapActivityを継承したクラス内にonTouchEvent(MotionEvent event)を書いてもイベントをキャッチできない。

以下のようなコードを書いても、MapViewの方にイベントが取られて、ログが出力されない。

public class MapDemo extends MapActivity {
 @Override
 public boolean onTouchEvent(MotionEvent event) {

  String action = "";
  switch (event.getAction()) {
  case MotionEvent.ACTION_DOWN:
   action = "ACTION_DOWN";
   break;
  case MotionEvent.ACTION_UP:
   action = "ACTION_UP";
   break;
  case MotionEvent.ACTION_MOVE:
   action = "ACTION_MOVE";
   break;
  case MotionEvent.ACTION_CANCEL:
   action = "ACTION_CANCEL";
   break;
  }

  Log.i("onTouchEvent", "action = " + action + ", " + "x = "
    + String.valueOf(event.getX()) + ", " + "y = "
    + String.valueOf(event.getY()));

  return super.onTouchEvent(event);
 }
}

そのため、MapViewクラスを継承したオリジナルのMapViewクラスを作成し、そのクラス内にonTouchEvent(MotionEvent event)を記述した。その時に気をつけること。


・MapViewクラスを継承する場合は、MapViewクラスのコンストラクタMapView(Context context, AttributeSet attrs)を必ずオーバーライドする

そうしないと、デフォルトのズームコントローラーを使ったり、フリップしてマップを動かすことができない。
静止画像のようにマップが表示されるだけの画面になってしまう。

コードは以下。

public class CustomizedMapView extends MapView {
 public CustomizedMapView(Context context, AttributeSet attrs) {
  super(context, attrs);
 }
 @Override
 public boolean onTouchEvent(MotionEvent event) {

  String action = "";
  switch (event.getAction()) {
  case MotionEvent.ACTION_DOWN:
   action = "ACTION_DOWN";
   break;
  case MotionEvent.ACTION_UP:
   action = "ACTION_UP";
   break;
  case MotionEvent.ACTION_MOVE:
   action = "ACTION_MOVE";
   break;
  case MotionEvent.ACTION_CANCEL:
   action = "ACTION_CANCEL";
   break;
  }

  Log.i("onTouchEvent", "action = " + action + ", " + "x = "
    + String.valueOf(event.getX()) + ", " + "y = "
    + String.valueOf(event.getY()));

  return super.onTouchEvent(event);
 }
}
この時のレイアウトは以下。
<?xml version="1.0" encoding="utf-8"?>
<hoge.MapDemo.CustomizedMapView
 xmlns:android="http://schemas.android.com/apk/res/android"
 android:id="@+id/mapview"
 android:layout_width="fill_parent"
 android:layout_height="fill_parent"
 android:clickable="true"
 android:apiKey="apiKey" />
MapView呼び出しはこんな感じ。
public class MapDemoActivity extends MapActivity {
 @Override
 public void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.main);

  /** マップビュー作成 */
  CustomizedMapView mapView = (CustomizedMapView) findViewById(R.id.mapview);
 }
}

2010年10月22日金曜日

Buzzの日付書式のフォーマットパターン

Google Buzz APIで結果を取得したときの日付書式は、以下のもの。

2010-10-15T06:51:04.000Z

JavaのSimpleDateFormatクラスへ、この日付書式をパースするフォーマットパターンを与えると、以下になる。

yyyy-MM-dd'T'HH:mm:ss.SSS


使い方としてはこんな感じ。
private static final SimpleDateFormat FORMATTER_INPUT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS");
private Date published;

 public void setPublished(String published) {
  try {
   this.published = FORMATTER_INPUT.parse(published.trim());
  } catch (ParseException e) {
   throw new RuntimeException(e);
  }
 }

追記 2010.11.01
android.text.format.Timeクラスのparse3339()を使うと、もっと簡単に解析できることが分かりました。
明日に向かって昇龍拳: Buzzの日付書式のフォーマットパターンをTimeクラスのparse3339()を使って簡単にパースする

2010年10月21日木曜日

公聴会には社長だけでなくトヨタ一丸で出席していたのかもしれない | トヨタはリコールを乗り越えたか?:日経ビジネスオンライン

トヨタはリコールを乗り越えたか?:日経ビジネスオンライン

米国トヨタ自動車販売社長のジェームス・レンツ氏は、今年2月に米議会の公聴会へ出席したとき、背後に、社員やディーラーの気を感じたと語っている。

公聴会は、トヨタの命運がかかった、とても重要な場だったろう。多くの社員やディーラーがかたずをのんで見ていて、そんな彼らの気が公聴会に居る社長へ伝わったのかもしれない。


私は、全社一丸となって~なんて、アメリカの会社文化の中にはないのでは、と思っていた。
が、トヨタには彼らを束ねる価値観があるようだ。この記事の中で社長が語っていた、トップから社員まで同じ現場に立って課題に取り組んでいく「トヨタウェイ」というスピリットが、それなのかもしれない。


ディーラーの方が語った「オレはトヨタが好きなんだ」という言葉。
この言葉は作り手にとって何よりも嬉しいご褒美だろう。そんな言葉をもらうために、私は私のものづくりを頑張ろう。


それで結局、この公聴会で議論になった問題はどうなったのか。
どうやら多くの場合は運転ミスが原因だったようだ。

トヨタ急加速は大半運転ミス? 電子制御異常なし 米運輸省調査 - SankeiBiz(サンケイビズ)

58件のうち35件はブレーキが踏み込まれておらず、運転ミスの可能性が高いことを示している。5件からはデータを得ることができなかった。(・・・)急加速の原因は電子制御スロットル装置の異常ではないというトヨタの主張に沿った形といえそうだ。(・・・)NHTSAから、安全問題の対応をめぐり1637万5千ドルの民事制裁金の支払いを課された。

ええー!、結局、自分たちの主張が正しかったと結論づけられようとしているのに、1637万5千ドル(日本円で約14億円)も制裁金を支払い、リコールに対応して・・・。大変だったろう。

公聴会では、トヨタに対して「恥を知れ」と言った女性もいた。
「トヨタは恥を知れ」 急加速体験の女性、米公聴会で証言 写真3枚 国際ニュース : AFPBB News

でも、その方が売却したレクサスの新しいオーナーは、なんの問題もないと言っている。
米国 / 急加速したトヨタ車、その後はトラブルなし / The Wall Street Journal, Japan Online Edition - WSJ.com


人生には、本当に理不尽で「まさか」と思う事が起こることがあるのだろう。

でもトヨタはこの経験を教訓にして、来年からアメリカでは全車にブレーキ優先機能を標準装備し、さらに安全な車にしようとしている。
トヨタ、全車にブレーキ優先機能を標準装備 来年から米国で (産経新聞) - Yahoo!ニュース


がんばれ、トヨタ。

2010年10月20日水曜日

AndroidではJSONとAtomのどちらが速いの?

APIの呼び出しでは、JSONとAtomの両方のフォーマットでレスポンスを取得できる場合が多いが、どちらがパフォーマンスがよいのだろう?

同じような疑問を持った人がいるようだ。
Benchmarking JSON vs XML Parsing in Android - ubikapps.net

彼はAtomをSAXでパースする方が、JSONより3倍も速かったと言っている。
So despite having smaller response sizes, overall JSON is 3 times slower! For the foreseeable future I’ll stick with the Atom reading list and SAX. Hopefully a future version of Android will have JSON streaming support built in.

接続環境や機体によって結果は変わるだろうし、またチューン次第ということもあり、一概にAtomの方がよいとは言えないかもしれないが、データとして参考にしよう。


ちなみに、AtomのパースはDOMよりSAXの方が速いようだ。
Android XML Parser Performance — Developer.com

Google Buzz APIで検索した時に返ってくるデータフォーマット(Atom形式)

TwitterのSearch APIと同じように、Google Buzz APIでも、searchクエリにパラメータを与えて、HTTP GETリクエストを発行して検索する。

例:
https://www.googleapis.com/buzz/v1/activities/search?lat=42.370498&lon=-71.083603&radius=100&prettyprint=true&alt=atom

「いつ、どこで、誰が、何をつぶやいたのか」という情報だけに限ると、チェックするタグは、以下にまとめたものくらい。

※Atom形式
<feed>

  <entry>
    <!-- メッセージ -->
    <title>あいうえお</title>

    <!-- 投稿日 -->
    <published>2010-10-15T06:51:05.485Z</published>

    <author>
      <!-- gmailアカウントの姓名 -->
      <name>おおつかみさほ</name>

      <!-- ユーザのサムネイル画像パス -->
      <link rel='photo' type='' href='' />

    </author>
    <activity:object>
      <buzz:attachment>
        <!-- 添付写真パス -->
        <link rel='enclosure' type='' href='' />

        <!-- 添付写真のプレビュー画像(小さめ)パス。1枚でも複数のタグが含まれる? -->
        <link rel='preview' type='' href='' />

      </buzz:attachment>
    </activity:object>

    <!-- 投稿された場所の緯度経度 -->
    <georss:point>42.370498 -71.083603</georss:point>

  </entry>

  <!-- 以下、結果件数だけ、entryタグが連続する -->

</feed>
これだけ押さえていれば、「いつ、どこで、誰が、何をつぶやいたのか」を表示できる情報が最低限集められる。


ちなみに、レスポンスデータの全タグを列挙すると以下になる。
<?xml version="1.0" encoding="UTF-8"?>

<feed>
  
 <!-- httpsから始まる、発行したリクエストのURI -->
 <link rel="" type=""/>
 <!-- 検索結果をブラウザなどで表示した時のタイトル -->
 <title></title>
 <!-- 検索結果内の最も新しい更新日 -->
 <updated></updated>
 <!-- この検索のID -->
 <id></id>
 <generator uri="http://www.google.com/buzz">Google Buzz</generator>
 
 <entry gd:kind="buzz#activity">
  <title></title>
  <published></published>
  <updated></updated>
  <!-- このBuzzのID -->
  <id></id>
  <!-- このBuzzのURI -->
  <link rel="alternate" type="text/html" href=""/>
  <!-- このBuzzのフィード -->
  <link rel="self" type="application/atom+xml" href=""/>
  <!-- このBuzzのコメントのフィード -->
  <link rel="replies" type="application/atom+xml" href=""/>
  
  <author>
   <!-- ユーザID(数字)-->
   <poco:id></poco:id>
   <poco:photoUrl></poco:photoUrl>
   <name></name>
   <!-- ユーザのプロフィールURI-->
   <uri></uri>
   <link rel="photo" type="" href=""/>
   <activity:object-type>http://activitystrea.ms/schema/1.0/person</activity:object-type>
  </author>
  
  <!-- メッセージ。titleと同じもの -->
  <content type="html"></content>
  <activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb>
  
  <activity:object>    
   <activity:object-type>http://activitystrea.ms/schema/1.0/note</activity:object-type>
   <!-- メッセージ。titleと同じもの -->
   <content type="html"></content>
   <buzz:original-content type="text"/>
   <!-- このBuzzのURI -->
   <link rel="alternate" type="text/html" href=""/>
   <!-- このBuzzに添付されたデータ -->
   <buzz:attachment>
    <activity:object-type>http://activitystrea.ms/schema/1.0/photo</activity:object-type>
    <link rel="enclosure" type="image/jpeg" href="/>
    <link rel="alternate" type="text/html" href=""/>
    <link rel="preview" type="image/jpeg" href=""/>
   </buzz:attachment>
  
 </activity:object>
  
  <source>
   <activity:service>
    <title>Mobile</title>
   </activity:service>
  </source>
  
  <!-- このBuzzを見る権限-->
  <buzz:visibility>
   <buzz:aclentry type="group">
    <poco:id>G:@me:@public</poco:id>
    <uri>https://www.googleapis.com/buzz/v1/people/@me/@groups/G:@me:@public?alt=atom</uri>
    <poco:name>Public</poco:name>
   </buzz:aclentry>
  </buzz:visibility>
  
  <!-- likedの件数やURI -->
  <link rel="http://schemas.google.com/buzz/2010#liked" type="application/poco+xml" href="" buzz:count="0"/>
  <georss:point></georss:point>
  <!-- 郵便番号など-->
  <georss:featureName></georss:featureName>
  
  <poco:address>
   <!-- 投稿されたロケーションをフォーマットした文字列 -->
   <poco:formatted>X-X 〇〇町, Atsugi, Kanagawa Prefecture, Japan</poco:formatted>
  </poco:address>
 
</entry>

</feed>

BuzzのデフォルトのレスポンスフォーマットはAtom。TwitterはJSON推奨で、Atom形式も現状は使用できるが、今後はサポートされなくなる。

PubSubHubbubもフィードが必要だし、BuzzはAtomとの親和性が高いんでしょうか。

2010年10月19日火曜日

iPhoneのカメラで撮影した写真を含んだ投稿をBuzzへポストすると、ロケーション検索結果に含まれる

Google Buzz APIで、lat/lon, radiusを指定してバズを検索したとき、位置情報付きで投稿した覚えがない、夫のバズが検索結果に含まれていた。



調べてみると、バズ自身に位置情報が付けられていなくても、iPhoneのカメラで撮影した写真をバズに付けた場合、写真のヘッダに含まれている位置情報が読み取られて、検索結果に含まれるということが分かったw(゚o゚)w。

ユーザは自分では位置情報を付けた覚えがないので、まさか自分の投稿がロケーション条件付き検索に含まれるとは思わないだろう。

出来れば、Google Buzz API側で、写真の位置情報はロケーションの割り出しには使わない仕様へ変更した方がよいと思う。


ユーザとしてこれを防ぎたい場合は、iPhoneのカメラで撮影した時に、位置情報を付けない設定に変更するとよい。

iOS3.xまではオール・オア・ナッシングで、全てのアプリケーションで位置情報が利用されるのを許可するかしないかだったが、iOS4ではアプリケーション毎に位置情報のオンオフが選択できるようになったので、便利だ(^-^)。

変更方法は以下のとおり。

iPhoneの「設定」 > 「一般」 > 「位置情報サービス オン」をタップ > カメラのオンをオフへ変更

詳しくは下記の記事が参考になる。
琴線探査: iPhoneの標準カメラアプリで位置情報がついてしまう事への対処法(iOS4以降)


Buzzで位置情報付きで検索すると、上の例も含めて、位置情報が付いたバズしか結果に含まれないので、データが見やすくて良い。

それに、レスポンスデータのサンプルもAPIドキュメントに載っているので、学びやすいなあ( ̄ー ̄)。

Google Buzz API にはrate limitingはあるのか?geoで検索は可能か?

Google Buzz APIを調査。

今年のGoogle I/OでBuzz APIがお披露目されてから5ヶ月程経つ。まだそれほど利用出来る機能はないかなと思っていたが、予想以上にAPIが整理されていて驚いた。

・RESTでBuzz APIを利用するためのドキュメント
Developer's Guide (v1): Using REST - Google Buzz API - Google Code

・OACurl(curlを使ったコマンドラインユーティリティ)を使ってBuzz APIを利用するためのドキュメント
OACurl Cookbook (v1) - Google Buzz API - Google Code


TwitterのAPIで気になっていた点である、位置情報に基づく検索は可能か、リクエストに回数制限はあるのかなどをまず調べる。


1. 位置情報に基づく検索は可能か

可能。

・lat/lon, radiusを指定してのBuzz検索
例:
https://www.googleapis.com/buzz/v1/activities/search?prettyprint=true&lat=42.370498&lon=-71.083603&radius=100&alt=atom

・places IDを指定してのBuzz検索
例:
https://www.googleapis.com/buzz/v1/activities/search?prettyprint=true&pid=CkQ6AAAA11-ykKK8mNCmfSTu9mOJzycQEMmYhAHcmOBt_G0IfZaA-0bPCDUTZaLKnskzqipJi3ljzUqlJ3SUvqL2GpyThRIQOPA13i2LQ3ttW4skW22YyxoUzIpT90QeEa9nswO7XqpywYuyErQ&alt=atom

・バウンディングボックスを指定してのBuzz検索
例:
https://www.googleapis.com/buzz/v1/activities/search?prettyprint=true&bbox=-79.38728,43.64183,-79.36265,43.66737&alt=atom


また、lat/lon, radiusなどでフィルタリングして、なおかつ、firehoseのようなリアルタイムな更新情報を取得することも出来る。但し、PubSubHubbubを使うのが前提。

例:
https://www.googleapis.com/buzz/v1/activities/track?q=query&lat=latitude&lon=longitude&radius=radius

このリクエストの結果では、PubSubHubbubのリアルタイム更新で必要となるフィードのメタデータが取得できる。


2. リクエストに回数制限(rate limiting)はあるのか

ある。

書き込みや更新系の処理は、1ユーザあたり1500回/時。
その他のリクエストは、1IPあたり50回/秒。
制限を緩和して欲しい場合はフォーラムに問い合わせてくれとあるが、十分な数だ。

Are Buzz API requests rate limited?
Yes, requests to write or modify data are limited to 1500 requests per hour, per user. All other requests are limited to 50 requests per second, per IP address. If you require a higher limit, please inquire in the forum.


ちなみに、PubSubHubbubを使った時もこのrate limitingが適用されるのかは分からない。


次は、実際にAPIを試してみて、具体的なlat/lanを含んだBuzzがどれだけあるのか確認してみよう。

ジョブズ氏曰くiOSとAndroidの違いは「integrated versus fragmented」

アップル社の第4四半期収益の発表の場にスティーブ・ジョブズ氏が登場し、5分ほどスピーチをしたようだ。
その模様がYouTubeで「見れないが、聞ける」。It’s a must-listenだ。

彼が逆さまなのはご愛嬌^^?



このスピーチの中で、ジョブズ氏は、

GoogleはAndroidをオープンでiOSをクローズドと特徴づけようとしている。でもそれは不誠実だ。iPhoneとAndroidの本当の違いは”統合されているか、バラバラか”ということだ、と語っている。
Google likes to characterize Android as open and iOS as closed. We think this is disingenuous. The real difference between the iPhone and Android is, “integrated versus fragmented.”

確かに、様々な種類のOSバージョン、画面サイズや、インターフェースの違いなど、機種の違いにあわせてテストしたり、異なるバージョンをマーケットに用意したりという手間が、Androidの場合は必要だ。

iOSでは、Appleが用意している幾つかの動作環境のみに集中すればよいので、開発者は真にクリエイティブなことだけに注力できるかもしれない。


でもね、ジョブズ。
一生懸命企画して、開発して、テストして、それでもAppleの審査に通らない、リリースできない場合があるというのは、開発サイドにとってはすごく負荷が高いしリスキーなことなのですよ。

作れば直ぐにマーケットに出せるという自由さは、何ものにも代えがたい価値のように、最近私は痛感しています。

2010年10月18日月曜日

靖国神社へ初めて参拝する

日本人として一度は行かねばと思っていた靖国神社だが、先日ようやく参拝できた。

まず、大鳥居の大きさに驚いた。「空をつくよな大鳥居」と歌われていたそうだが、正にそのとおり。

沿道には銀杏の木が等間隔に並んでいた。銀杏が色づく11月下旬頃に靖国神社を訪れると、とても美しい風景が見られるだろう。

神門と第二鳥居の間の、上空がぽっかり空いた空間がとても清々しくて心地良かった。この日はちょうど快晴で、見上げると青く澄み渡った空が広がっていた。本当にここは清らかな空間で聖地なのだなと感じる。

土曜日だからか、参拝者が多かった。電車の時間を気にしてらっしゃる方もいて遠方からいらっしゃったのかもしれない。また、海外からいらっしゃった方も多く、ハングルや中国語、フランス語、英語など、いろんな言葉が聞こえた。靖国神社は日本人だけでなく海外の方も参拝するような大きな神社なのだなと感じる。


今月の社頭掲示は楠瀬益實命の「魂は永遠に生きる」だった。

「人類の幸福、世界の平和の礎のために之が最適の死に場所だ」

楠瀬殿が書き記された言葉は、自らの死が、祖国のみならず世界、人類の平和につながるようにと願ったものだと思う。繰り返し読む。


拝殿の脇には槍が立てられていた。

平和は自ら勝ち取るものだという意味なのか。それとも何かいわれがあるのだろうか。
祖国のために散っていった方々の冥福をお祈りし感謝を申し上げる場所という印象を靖国神社に持っていたので、こんなに大きな槍が拝殿前に立っていたのは、少し驚きだった。


拝殿へ参拝した後は、遊就館を見学する。

「御祭神の遺徳を尊び、また古来の武具などを展示する施設」として構想された遊就館。だからからか、鎧兜、日本刀、弓矢、大砲、零式艦上戦闘機、人間魚雷回天に到るまで多くの武器が展示されていた。

遊就館の展示や置かれている配布物で気になったのは、大東亜戦争初期などで「赫々(かくかく)たる戦果をあげた」という記述だ。

赫々たる戦果は英語で表現すると、win a glorious victoryとでも言うか、つまりgloriousでbrilliantな成果という意味だ。

更に、それだけ成果があったということは、その裏には当然命を落とした敵軍の方が多くいるということだろう。

国のために散っていかれた御霊に祈りを捧げる靖国神社に併設されている施設として、ふさわしい記述なのか気になった。


遊就館は予想以上に展示フロアが広くて、2時間ほど見て回ったが、最後は駆け足になってしまった。

遊就館に行きたかったのは、祖国、つまりは今ある私達を守るために、大東亜戦争や支那事変などで亡くなった方々のお写真やお手紙を拝見するなどして、彼らに会いに行くことだった。だが、それらは最後の方に展示してあり、残念ながらじっくり見る時間がなかった。

次の機会にはもっと時間を作って、じっくり拝見したいと思う。

TwitterのStreaming APIでlocationsを指定する方法とSearch APIでgeocodeを指定する方法

位置情報付きのツイートを利用するには、Streaming APIでlocationsを指定する方法と、REST型Search APIでgeocodeを指定する方法の2種類がある。

どちらにも、ご利用上の注意があり、どちらが正解とも言えない。
明日に向かって昇龍拳: TwitterのStreaming APIでlocationsを指定する場合の注意点
明日に向かって昇龍拳: TwitterのSearch APIでgeocodeを指定する場合の注意点


しかし個人的には、位置情報付きのツイートを取得するには、現時点では、Streaming APIでlocationsを指定する方がよいと思う


Streaming APIを使うメリット: 1. rate limitingがない

マップをインターフェースにするなどして、頻繁にTwitterのサーバへリクエストを発行するような仕様にする場合には、回数制限というのは大きなボトルネックだ。Streaming APIでは、一度開いた接続は基本的に無期限だ。回数制限という概念がない。


Streaming APIを使うメリット: 2. geo要素がnot nullなツイートしか検索結果に含まれない

位置情報が必ず指定されている、つまりgeo要素がnot nullなツイートしか検索結果に含まれないので、結果データが扱いやすい。

Search APIではやたらめったら結果が含まれてくるが、その中から使えるデータを精査して取り出すなど手を加える必要がある。作るときにも、その面は手間がかかるし、アプリケーションのパフォーマンスにも、コストとして影響するだろう。

更に、Search APIでは位置情報が付いたツイートが何ページ目に出現するのか分からないので、結局多くの結果にアクセスする必要が出てくる。当然、rate limitingに引っ掛かり易くなる。

geoがnot nullなツイートに限られることは、結果数が少なくなるというデメリットでもある。
しかしそれは、位置情報が付いたツイートが増えるように位置情報を付け易いクライアントを別途用意したり、少しずつでも位置情報付きのツイートが将来的に増えていくことで、状況も変わっていくだろう。


Streaming APIを利用するには、OAuthの利用や、切断時のリトライなど、実装しなくてはならないことが幾つかあるが、それらを考えても、やはり、上記のメリットの方が現時点では上回る。

TwitterのSearch APIでgeocodeを指定する場合の注意点

TwitterのREST型Search APIでは、geocodeをパラメータに含めて、ある地点の近くのツイートを取得することができる。いわゆるnearby検索だ。しかし、実際に使ってみると問題が多々ある。


1. geoがnullなツイートがほとんど

検索結果に含まれるツイートは、ほとんど(95%以上)geo要素がnullで、位置情報が含まれていないものばかり。

では、どうやって位置情報がプロットできるツイート数を増やすか。

まずは、プロフィールに入力されている住所をジオコードしたり、「iPhone:35.xxx, 132.xxx」などの文字列を自分でパースして緯度経度を取得したりする方法が考えられる。手間だが、これを行えば、100件中10件ほどは位置情報がプロットできるツイートになる。

次に、検索結果は最大約1500件取得できるので、1ページあたり最大100件で、ページナンバーの値を1から15まで変えながら、つまり15回リクエストを発行すれば、位置情報がプロットできるツイート数を増やせるだろう。しかし、この場合はrate limiting(IPベースのリクエスト制限回数)をオーバーしないか、注意が必要だ。


2. 検索結果が必ずしも指定した条件に合致していない

検索条件にgeocode及び半径 N km と指定しても、指定条件に当てはまりそうにないツイートが検索結果に入ってくる。検索対象が日本のどこかの場合は、現住所に日本と書かれているユーザのツイートも検索結果に含むというのが、Twitter側の仕様のようだ。


3. 検索結果の並び順が指定できない

ツイート日の降順に検索結果は並んでいる。指定したgeocodeに近い順には並んでいないので、そのようなリストを作りたかったら自分でソートする必要がある。


4. 検索結果が安定しない

同じクエリーを投げても、検索するたびに検索結果が変わる。なぜだかは分からない。


結論としては、「TwitterのSearch APIで、指定した緯度経度から半径 N km内のツイートを全て取得する」 ことは、簡単ではないということだ。

rate limitingを上回らないように気をつけながら、独自のパーサーなども絡ませて位置情報をプロットできるツイート数を増やして、使っていくことになる。

rate limitingを気にしたくなければStreaming APIを使う手もあるが、それも決してバラ色の解決策ではないのだ。
明日に向かって昇龍拳: TwitterのStreaming APIでlocationsを指定する場合の注意点

2010年10月16日土曜日

TwitterのREST型Search APIを使う場合の注意点

TwitterのSearch APIを使う時は、以下の点に注意する。


1. ユーザ認証は不要

Search APIの利用には認証は必要ない。誰でもリクエストが可能。



2.rate limiting(IPベースの制限回数)がある

Rate Limiting | dev.twitter.com

ユーザ認証が要らず、アノニマスで接続できる分、1IPあたり1時間内に発行できるリクエスト回数が限られている

不必要な検索や荒らしを防ぐために、それが何回なのかは公開されていない。Search APIと同じくREST型のREST APIは1時間あたり150回だが、ちなみにそれよりは多いらしい。多くのアプリケーションでは十分ではなかろうかというくらい、とある。

Requests to the Search API, hosted on search.twitter.com, do not count towards the REST API limit. However, all requests coming from an IP address are applied to a Search Rate Limit. The Search Rate Limit isn't made public to discourage unnecessary search usage and abuse, but it is higher than the REST Rate Limit. We feel the Search Rate Limit is both liberal and sufficient for most applications and know that many application vendors have found it suitable for their needs.

でも気をつけないといけないのは、IPあたりという点だ。

Search APIを使った自分のプログラムのリクエスト以外にもTwitterのAPIを呼び出しているものがあれば、それもSearch APIの制限回数の中に含まれる。

つまり、何らかのTwitterクライアントを動かしていたり、他のサービスでTwitterのAPIを使っている場合は、それらもSearch APIの回数にカウントされるので、その分も考慮しないと、デバッグしているだけで制限回数をオーバーしかねない。

制限回数をオーバーするとHTTPステータスコード 420 がTwitterのサーバから返ってくる。

あまりにも制限回数を超えるような高負荷なリクエストを連続してTwitterへ送った場合は、ブラックリストに登録されることもある。ブラックリストに載ったかもと思ったらAPIサポートページに相談して解除を申請することになる。



3. User-Agentを含まないとリクエストの制限回数がより少なくなる

Search APIによるリクエストには、ユニークで識別可能なUser-Agentが含まれている必要がある。でないと制限回数はより少なくなってしまう。



4. 制限回数を増やすような相談は可能だが、まずは自分でやることやってから

REST APIにはホワイトリストがあり、申請が認められれば1時間あたり20,000回まで制限が緩和できる。
が、Search APIの場合は、まずは自分でパフォーマンスチューニングをしてください、と。もうこれ以上は無理だ!となった場合にしか、APIサポートページに相談してこないでね、とある。

There isn't a whitelist for the Search API in the same way there is for the REST API. However, under some rare circumstances we have worked with developers to raise rate limiting for their applications search requests.

We do not give preemptive whitelisting for the Search API though. You must have a working application that has proven it requires more capacity before we will discuss whitelisting. If you feel that your application is doing everything it can to limit and combine queries where appropriate you can contact the email address on the API support page to discuss your needs.

また、IPベースかユーザーアカウントベースでホワイトリストへの申請が可能なREST APIとは異なり、Search APIではIPベースしか認められていない。

制限回数がとてもシビアな問題な場合は、Streaming APIを使ってね、ということだ。

TwitterのStreaming APIでlocationsを指定する場合の注意点

TwitterのStreaming APIを使って、プッシュ型で位置情報付きのツイートを取得する時は、以下の点に注意する。


1. Geotagging APIを使用して作成され、且つ、追跡する対象領域内に位置するツイートだけが結果に含まれる

つまり、ユーザーのプロフィールにある現在地の情報はツイートのフィルターには使われない

もし、ユーザーがプロフィールの現在地に“San Francisco”と設定していても、そのツイートがGeotagging APIを使っていなくてgeo要素がなければ、検索結果のストリームには含まれない。

Only tweets that are both created using the Geotagging API and are placed from within a tracked bounding box will be included in the stream – the user’s location field is not used to filter tweets (e.g. if a user has their location set to “San Francisco”, but the tweet was not created using the Geotagging API and has no geo element, it will not be included in the stream).


2. 追跡する対象領域はカンマで区切った経度と緯度のペア指定

最初のペアは対象領域の南西の角を示す。
例えばlocations=-122.75,36.8,-121.75,37.8はサンフランシスコエリアのツイートを追跡する指定になる。

バウンディングボックス、つまり追跡する対象領域は複数の指定ができて最大25個まで可能。例えばlocations=-122.75,36.8,-121.75,37.8,-74,40,-73,41はサンフランシスコとニューヨーク市エリアのツイートを追跡する指定になる。

Bounding boxes are specified as a comma separate list of longitude/latitude pairs, with the first pair denoting the southwest corner of the box. For example locations=-122.75,36.8,-121.75,37.8 would track tweets from the San Francisco area. Multiple bounding boxes may be specified by concatenating latitude/longitude pairs, for example: locations=-122.75,36.8,-121.75,37.8,-74,40,-73,41 would track tweets from San Francisco and New York City.

(・・・)and you may specify up to 25 bounding boxes.


3. locationsの指定とtrackの指定の両方を行なうとOR検索になる

なんと、対象領域の指定が論理ORなのである。

よって、検索キーワードの指定(trackの指定)に「twitter」、追跡する対象領域(locationsの指定)に-122.75,36.8,-121.75,37.8を行なうと、 twitterを含むツイート(geo要素がないツイートでも)か、あるいはサンフランシスコエリアのツイートという条件を指定したことになる。

Bounding boxes are logical ORs. A locations parameter may be combined with track parameters, but note that all terms are logically ORd, so the query string track=twitter&locations=-122.75,36.8,-121.75,37.8 would match any tweets containing the term Twitter (even non-geo tweets) OR coming from the San Francisco area.


特に、1と3が問題だ。

3は、ドキュメントを読んでいて、「ちょっとちょっと~」とついつい突っ込みたくなった。なんでORなの?そこはANDでしょう。

しかし、Twitter側で実装していないのであれば、ストリームで送られてくる検索結果を自分でフィルタリングすればよいだけのこと。まあ、まだよい。


しかし、1の方は大きな障害になるかもしれない。

1は、Twitter側のアプローチとして考えると、正しい。位置情報で絞り込みたいので、位置情報を指定していないツイートは検索結果に含まれるべきではない。

但し、まだまだgeo要素を含んだツイートは少ない。geoがnullでないツイートに限ると、結果件数はとても寂しいものになるだろう。そんな状態でアプリケーションを作っても、ゴーストタウンのようになってしまうかもしれない。何らかの工夫が必要だ。

逆に、プロフィールの現在地をストリームで取得する方法があるのか調べてみたが、今のところストリーム系の3つのAPI、Streaming APIUser StreamsSite Streamsのどれにもそれらしい記述は見当たらない。


ところで、Streaming APIは、2010年8月31日からベーシック認証がdeprecatedになり、OAuthを使うこととなったので、それもチェックポイントだ。
Transitioning from Basic Auth to OAuth | dev.twitter.com

OAuthの具体的な使用方法は、ドキュメントをまだ詳しく読んでないので分からないが、のっけから、「Transitioning from Basic Authentication to OAuth isn't simple for everyone. (ベーシック認証からOAuthへ切り替えることはすべての人にとって簡単というわけではない)」とあるゾ。

2010年10月15日金曜日

ハンズフリー状態のGoogle Carの映像を初めて見た。スゴイなあ。

ABC Newsで運転手なしで動いているGoogle Carの取材が行われていた。




手を離しても勝手にハンドルが動いている模様や、チキンレースのようにわざと車の前に立ってみたりといった模様が放送されていた。

勝手にハンドルが動いて車が動いている模様を実際に目にすると、本当にスゴイなあとしみじみ実感する。
パラダイムが違います。

プリウスが改造されているのでハンドルにトヨタのマークが付いていて、それも、日本人として誇らしく感じる。


更に、取材したオークランドに居る記者とメインキャスターとが、Skypeを使って生放送でやりとりをしているのにも驚いた。ラグもなくスムーズにやりとりが出来ている。

このようなスタイルがアメリカの地上波のニュースの中で行われているのか。こうなると中継車などは将来無くなるかもしれないなあ。

2010年10月14日木曜日

json指定でSearch APIを使ってリクエストを投げた時にTwitterのサーバから返ってくるデータ形式

json指定で、Search APIを使ってリクエストを投げた時に、Twitterのサーバから返ってくるデータ形式のまとめ。

「results」というルートの下に以下の形式のデータが収まっている。

  • location
    プロフィールに設定している現在地。

  • profile_image_url
    プロフィールに設定しているアイコン画像のパス。

  • created_at
    つぶやき日時(GMT+00)。

  • from_user
    つぶやいたアカウント名。misahotなど。

  • metadata
    recent(最近のもの)やpopular(人気のもの)などの検索結果形式。

  • to_user_id
    誰宛につぶやいたのか。誰宛でもない場合はnull。

  • text
    エンコードされたつぶやき文字列。

  • id
    ツイートID。

  • from_user_id
    つぶやいたユーザID。32102100などTwitter内で管理されている番号。

  • geo
    位置情報。位置を含まない場合はnull。

  • iso_language_code
    つぶやいた言語のISOコード。jaなど。

  • source
    ツイートに使ったアプリケーションのリンクタグ。<a href="http://twitter.com/">web</a>など。

世代別選挙区は逆に世代間抗争を生まないか? | サンデル教授に問いたい「搾取」の正当性:日経ビジネスオンライン

「世代会計」という言葉を初めて知った。
世代会計とは、「国民が生涯を通じて、政府に対してどれだけの負担をし、政府からどれだけの受益を得るか」を推計する手法とのこと。

そして世代会計で算出してみると、60歳以上の世代の純負担はマイナスで約4000万円の得(受益超過)、50歳代は約990万円の得(受益超過)。それに対して、それ以降の世代の純負担はプラスで、将来世代は約8300万円もの損(支払超過)となっているらしい。

では、この世代間格差はあってもよいのか、正当性の根拠は何かと著者は続けている。

サンデル教授に問いたい「搾取」の正当性:日経ビジネスオンライン
世代間格差については、「今の老齢世代は戦争を経験しており、そのような金銭面で評価できない苦しみを受けた。それも勘案する必要がある」といった反論が聞かれる。この意見は一見もっともらしく聞こえるが、少なくとも、受益超過にある今の50歳代にこの前提は当てはまらない。

(中略)

この打開の方策の一つとして、東京大学の井堀利宏教授は、「世代別選挙区」の導入を提唱している。例えば、地域別に分かれている選挙区でなく、20代代表、30代代表、…、60代代表というように、世代別の代表を国会に送り込む方法である。


世代別選挙区はユニークなアイデアだが、20歳代表はこう思う、60歳代表はそれには反対だ、という議論をしていたら、ますます世代間の溝を広げ、「抗争」のようなエスカレーションを生むのではないか。

世代間格差は、公平性の面から、できるだけ解消されるべきだと私も考える。

でも世代別選挙区には反対だ。〇〇世代代表という区分を国民の中に作り出すのではなく、国民は一丸となるべきだ。


日本の喫緊の社会保障の問題こそ、国会で党議拘束を超えて、議論をして欲しい。

世代会計の数字を元に、世代ごとにアンバランスな状態にあることをどう解消していくか、各地域から選ばれた国会議員が大いに議論し、新しいモデルを作っていくべきだろう。
勿論、国会だけでなく、自分も含めてひとりひとりの国民も考えていくべきだろう。

現状のサービス内容を維持するために将来世代の負担がかかりすぎるのであれば、サービス内容を下げて、今いきている世代の負担額を増やしていくしかない。

自分だけのことを考えたら1円でも貰えるものはもらいたい。でも、みんながそんな私心を前面に出してしまったら、社会保障の問題は解決しない。

しかし、日本人のスピリットには、世代関係なく、公を考えるスピリットが生きていると、私は信じている。だから、世代別の代表を出して議論を戦わせずとも、問題は解決できると思っている。


ただし、選挙権は18歳から与えよう。

世代別ギャップを埋めるために、年齢が若い国民も選挙権を持とうということではない。

今年の5月に、国民投票法が施行されたが、国民投票の投票権は年齢満18歳以上の日本国民が有すると規定されている。
総務省|国民投票制度

ところが、年齢満18歳以上満20歳未満の者が国政選挙に参加できるようになるまでは、今と同じく、投票権は年齢満20歳以上の者に限られている。

つまり、国民投票法の規定と公職選挙法の規定の間にギャップがあり、それを解消しましょうと決められているのに、まだやれていない。

だから、早く18歳以上から国政でも国民投票でも投票できるように環境を整えよう。

やるべきことをとっととやろう。

2010年10月13日水曜日

SonyがGoogle TV用にAndroid Developer サイト開設

ソニー米法人が10月12日、つまり今日、「Google TV」プラットフォームを搭載したインターネットテレビ「Sony Internet TV」を発表した。
ソニー、「Google TV」搭載のネットテレビを発表 600ドルから (ITmedia News) - Yahoo!ニュース

それに合わせて、ソニーはAndroid開発者向けのプログラムも立ち上げて、開発者の登録を受け付けている。
今は何も情報がなく、表紙だけのトップページだ。
Android Developer Program

お知らせ等受け取れたらいいなと思ってSonyStyleにアカウント登録してみようとしたが、どうしても登録できない。米国以外の住所だと登録できないのかもしれない。

MyLocationOverlayを使うとDroidでクラッシュする場合がある

AndroidでGoogle Mapsを使っているときに、現在位置をMyLocationOverlayクラスを使って表示していると、Droidではクラッシュするらしい。

Androidアプリサービス開発者ブログ:Dorid & Droid X 端末でMapが強制終了してしまう件
The default MyLocationOverlay class crashes apps on Droid X | dimitar.me

上記の問題を解消したFixedMyLocationOverlayクラスを提供してくれている方がいらっしゃる。

特定の端末のみで発生するエラーに出くわすこともあるようだ。気をつけねば。

2010年10月1日金曜日

Android Marketで有料アプリが買える・売れる国がぐっと増えた

Android Developers Blog: More Countries, More sellers, More buyers

Android Marketで有料アプリを販売出来る国は、これまで数国に限られていたが、一気に20カ国以上増えて全部で29カ国になったようだ(※)。
Supported locations for merchants - Android Market Help
※上記のAndroid Marketのヘルプページでカウントすると30カ国だ

今回、有料アプリを販売できる国として追加されたのは、アルゼンチン、オーストラリア、ベルギー、ブラジル、カナダ、デンマーク、フィンランド、香港、アイルランド、イスラエル、メキシコ、ニュージーランド、ポルトガル、ロシア、シンガポール、韓国、スウェーデン、スイス、台湾。

これまでアジア圏内で有料アプリが販売できる国は日本だけだったが、香港、シンガポール、韓国、台湾が加わった。


また、2週間後には有料アプリが購入できる国が18カ国追加される。購入できる国は全部で32カ国になった(※)。
Android Market - Wikipedia, the free encyclopedia 「Availability for users」
※上記wikiページでカウントすると30カ国だ

今回、有料アプリを購入できる国として追加されたのは、アルゼンチン、ベルギー、ブラジル、チェコ、デンマーク、フィンランド、香港、インド、アイルランド、イスラエル、メキシコ、ノルウェー、ポーランド、ポルトガル、ロシア、シンガポール、スウェーデン、台湾。


iTunesのApp Storeには無料アプリだけでなく、多くの有料アプリがあるが、Android Marketは圧倒的に無料アプリの数が多い印象だ。

Google Checkoutの環境が整うことで、Androidアプリでも収益化が見込めるアプリケーションが増えるといいですね。