ブログ初め

非常に遅くなりましたが、本年もよろしくお願いします。


私事ですが、下の娘が生まれ、2月いっぱいまで育児休職しています。
休職していますが、上の子がまだ手のかかる年頃なので、家事やら子供の世話やら、いろいろとやることが多く、意外に自分の時間がなかったりします。もう少しいろいろ出来そうと目論んでいたのですが、あてが外れています。


下の子が生まれたあとの生活は、次のような感じでした。

  • 最初の1週間:昼間はひいおばあちゃんが娘の面倒を見てくれましたが、上の子と2人ぐらし状態
  • 次の1週間:仕事の引継と下の子の眼底検査のために病院受診
  • その後:年末年始から現在に至る

最初の1週間

上の子は初めてママがいない夜を5日間も味わったわけですが、意外にも泣いたのははじめの2日ぐらいで、あとはちゃんと聞き分けてくれました。
下の子が無事生まれ、家に帰ってすぐ、上の子にこう言い聞かせたわけです。
「大事なお話があるんだけど、聞いてくれる? ママは赤ちゃんを産むのに病院にいるんだよ、しばらくはパパと2人でいようね」

娘はお風呂から出ると、「マーマー」と叫んでママに突進していき、「キレイになったねー」と言ってもらうのが日課だったのですが、入院期間中は、「マーマー(少し探して)?・・・病院。」とか言っていました。小さいながらもよく理解しているものです。

実はこの一週間で食洗機の設置もやっていたりします。そろそろやっておかないと、と思っていたら出産が来てしまいましたが、家事を楽にするためにも、半ば無理矢理に食洗機を導入したわけです。
友人からレビューのリクエストがあったので、後日書いてみたいと思っています。

そして次の一週間

下の子が退院してまもなく、上の子と同じく、網膜芽細胞腫が無いかを調べるために、眼底検査を受けに行ったわけです。保険証が間に合わないので、資格証明を保険組合からもらったり、、とまあ、バタバタしたのですが、まずは検査して異常なしということで、安心しました。
下の子の眼底検査はものすごい勢いで泣いていてかわいそうでしたが、下の子も上の子と同様に経過観察を行っていきます。
仕事の引継もこの期間で、そして休職に入ったわけです。

年末年始から現在

クリスマスは、市販のロールケーキの巻きを解いて苺と生クリームを入れ、ちょっとデコレーションしました。上の子はイチゴが大好きなので、作っているときも、食べている時も大興奮でした。
しばらく、「ケーキ食べよっか?たべたいなー。」が口ぐせでした。

年末に急ぎで年賀状を作って出し、
そして、年が明け、
下の娘は、はやくも生後1ヶ月となりました。
しょっちゅう母乳をのみ、どんどん成長していきます。おデブちゃんになってきて、立派な二重あごが形成されています。体重は1kg増え、順調な成長っぷりです。また、目がさめているときに、微笑んだ表情をするようになりました。
下の子は先日はじめて湯船に浸かりましたが、浸かって早々にウンチされてしまい、大笑い。ビデオにバッチリ撮影されていい思い出です。下の子を洗っている間、黄色い浮遊物いっぱいの湯船で、上の子は固まっていてちょっとかわいそうでした。

義眼調整とお友達とのパーティー

本当に寒かった一日でしたが、今日は娘の義眼の調整のために、義眼師さんの工房へお邪魔しました。
さらに、その工房で、娘と数日違いの誕生日で、さらに同じ病気で、さらに同性のお友達と待ち合わせしていたわけです。
そのお友達、通称イチゴスキーさんは、とっても女の子らしいオーラをまとっていらっしゃるわけです。ついつい娘と比べてしまい、そして、同じ女の子なのにこうまでも違うものかと、うなってしまうわけです。本当にかわいらしい。笑うときもニコーってあまり音がしませんが、娘はガハハハ なんです。イチゴスキーさんはおとなしくしているのですけど、娘は、動き回って落ち着きません。しゃべりすぎでうるさくて、例の怖い存在な、「がぶがぶ」さんを利用して注意したり。
娘とイチゴスキーさんは、親の勝手な思いでは、親友になってほしいですが、こんなに違うと大丈夫だろうかと心配になります。
ケーキをご馳走になり、コーヒーをご馳走になり、持参したカステラを食べ、お互いの子供の成長について話したり、楽しいひと時を過ごせました。

調整のほうは、義眼試算との相談の結果、今の状態がよいので現状維持という結論になりました。研磨をしていただいて、これでコンディションはばっちりです。家族が増える前に、調整ができてよかった。

子供には怖いと思うものが必要なのかもしれない

肌寒かった土曜日に近くの公園でお祭りがあり、出かけてきました。
お祭りですが、おみこしはありません。模擬店が出ていて、舞台があって、ソーラン節の太鼓や、コーラス、フラダンスなど地域の方が参加していろいろやっている感じです。娘は、太鼓が好きみたいで、ノリノリで見ていました。
お昼近くの時間で、娘のお友達の家族と一緒にランチを食べながら、楽しんでいました。
そうやって、ちょっと寒い中、子供たちと駆け回って体を温めたりしながらすごしていると、舞台も後半になって、獅子舞が始まったわけです。
僕も小さいころに実家の地区のお祭りで見て以来、テレビ以外の実物の獅子舞はあまり見た記憶が無かったので、懐かしいなーと見ていたんです。あ、がぶっとやってもらうと、ご利益とかあったよなーと思って、「行ってくる!!」と妻と娘は、「がぶがぶ」されに舞台のほうに出て行ったのです。
娘は、やんちゃで、結構気が強く、いつも手を焼いているし、節分のときに鬼のお面をしていた私をまったく怖がらなかったりで、正直、あっけらかんとして戻ってくるんだろうなーと遠目に見ていたところ、妻が大笑いで帰ってきたわけです。

妻「超怖がってた! 大口開けて泣いて、ポップコーンがドバーって!!!」

娘は、獅子舞に行く直前に、子供限定で無料配布されていたポップコーンを食べていたのですが、その食べかけをぼろぼろ落としながら泣いたそうです。失敗しました。そんなリアクションするとは夢にも思わなかったので、静観していましたが、、、写真取りに行けばよかった。


そして、戻ってきた娘を追いかけるかのように、再度獅子舞到来!!!。


もう、パニックで大泣きです。一緒に来ていたお友達(3歳、2歳)も大泣きですごいことに。子供ってこういうの本当に怖いんですねぇ。


そして、帰宅後。いつものように言うことを聞かない娘に、こういってみたのです。
「言うこと聞かない子には、がぶがぶさん、またきちゃうよ」
ほんとうに、怖かったんですね。いままで何言ってもきかない子だったのに、ぴたりと駄々っ子をやめたではありませんか。。。魔法です。
イヤイヤの気持ちで騒ぎ立てて、何を言っても聞こえていない様子でしたが、この一言でこちらの言うことを聞くようになったのです。

そして、「がぶがぶ」さんのことを言わないで注意しても、以前よりこちらの言うことを聞いているようなのです。怖い存在のおかげなのかもしれません。
怖がっているので、あまり調子に乗ってはいけないですが、いけない子すぎる時には、「がぶがぶ」さんのお世話になろうと思います。


獅子舞のような子供が怖がるような事は、案外、子育てには大事なスパイスなのかもしれません。

娘の眼球摘出から一年

網膜芽細胞種の左眼球摘出から一年たちました。
娘は無事に1才半になってくれて、とても元気です。元気すぎて手がかかりすぎで、正直困ります。


ですが
元気であることが喜びです。


1歳半ですが、元気におしゃべりして、かなり意思疎通できます。一生懸命真似をして、一生懸命おしゃべりしています。一生懸命おしゃべりというより、静かにしていられないに近いかもしれません
妻の話を聞くと、児童館の幼児クラブの先生が、よくしゃべるので、びっくりしているらしいです。


「一人遊び? なにそれ、おいしいの?」
って言いそうなぐらい、にぎやかにしていて、かまってちゃんです。要は一人遊びがほとんどできません。


こちらが何をしていようと、お構いなしに
「いっしょに、いっしょに」
といって、手を引っ張って、遊び部屋に連れて行かれます。そして、


「すわって」
といって、その場で座るように要求されます。
そうして、一緒に遊びます。


言葉は得意なようですが、聞き分けはぜんぜんよくありません。「だめ」「い・やなの」「あーーー」って、わがまま放題です。
ご飯はたくさん食べないし、歯磨きが苦手です。奥歯の側面、歯茎側を嫌がります。怪獣が鳴いているような声で「あ”−−−」って大きな口をあけて、何とか逃れようとしています。
歯磨きは私の担当ですが、平日の夜にそれをやられると、ちょっと滅入ります。


髪の毛が少なめなためもあって、しょっちゅう男の子に間違われます。
笑い方が「がははは」みたくするときがあって、なんだかもう。
それでもピンクの洋服を着ているのだから、ちょっとは気づいてください。


ですが、いつまでも元気に、こんな時間が続くことを願っています。


お世話になった病院の先生、看護師さん、義眼師さん
お友達とそのママ、パパ
みんなありがとうございました。


これからもお世話になると思いますが、娘を、そして我が家をよろしくお願いします。


そして12月にまた家族が増えます。
次の子が娘と似たような性格だったら、どうしようかな。きっと、下の子はおとなしくて聞き分けのいい子です。誰かかなえてくれるんだったら、ぜひお願いしたいです。

TracGanttCalendarプラグイン をカスタマイズ(Trac 0.10)

会社で運用しているTracのバージョンはいまだに 0.10 です。
この記事も、Trac 0.10とTracGanttCalendarプラグインの0.10 brancheについてのものです。

svn checkout http://svn.sourceforge.jp/svnroot/shibuya-trac/plugins/ganttcalendarplugin/branches/0.10

ガントチャートが出ると、便利です!

超有用なこのプラグインですが、次のような不満があったりします。。

  1. チケットの並び順が...
    • チケット番号順でもない(?)です。できれば開始日で並べ替えると、前後関係が把握しやすくて吉ですよね
  2. 画面レイアウトの関係で、チケットの概要の文字数が多いと、省略されてしまい、一覧性に乏しい
    • このガントをプリントして、打ち合わせに使いたい時に困る。せっかくチケットを入れているのだから、この情報以外にExcelでガントを作りたくない!(ただ、年間計画とかの大きな粒度のスケジュールはExcelで作っていたりしますが・・・)
  3. プリントできない(消える)
    • TracのデフォルトのCSSが印刷時にForm要素を問答無用でdisplay:noneなので困ります


これらの不満を解消すべく、カスタマイズしてみました。
カスタマイズのポイント

  1. チケットの並び順は、開始日の古いもの順
  2. ガントチャートに、チケットタイトルを省略せずに表示する。(ついでにチケットへのリンクやHover、開始、終了日を表示する)
    • 多少表示はごちゃごちゃしてしまいますが、一覧性を優先した結果です。
  3. プリント可能に
    • TracCSSにある、Form.printableFormを使えるようにクラス名を付与する
  4. (追加で)デフォルトのソート対象をコンポーネントから、マイルストーンに変更
    • うちの環境では、この方が都合がよいです

カスタマイズ後の画面表示

  • ソートが開始日順となり、ガントチャートのバーの部分にチケットタイトルが表示される


  • チケットタイトルのHover


カスタマイズ内容

Index: ganttcalendar/ticketgantt.py
===================================================================
--- ganttcalendar/ticketgantt.py	(リビジョン 429)
+++ ganttcalendar/ticketgantt.py	(作業コピー)
@@ -52,7 +52,7 @@
         show_closed_ticket = req.args.get('show_closed_ticket')
         sorted_field = req.args.get('sorted_field')
         if sorted_field == None:
-           sorted_field = 'component'
+           sorted_field = 'milestone'
 
         if baseday != None:
            r = re.match(r'^(\d+)/(\d+)/(\d+)$', baseday)
@@ -112,22 +112,26 @@
         tickets=[]
         for id, type, summary, owner, description, status, due_assign, due_close, complete, item in cursor:
            due_assign_date = None
+           due_assign_date_short = None #assign date without year
            due_close_date = None
+           due_close_date_short = None #close date without year
            try:
               t = time.strptime(due_assign,"%Y/%m/%d")
               due_assign_date = date(t[0],t[1],t[2])
+              due_assign_date_short = date(t[0],t[1],t[2]).strftime('%m/%d')
            except ValueError, TypeError:
               continue
            try:
               t = time.strptime(due_close,"%Y/%m/%d")
               due_close_date = date(t[0],t[1],t[2])
+              due_close_date_short = date(t[0],t[1],t[2]).strftime('%m/%d')
            except ValueError, TypeError:
               continue
            if item == None or item == "":
               item = "*"
            if complete != None and len(complete)>1 and complete[len(complete)-1]=='%':
               complete = complete[0:len(complete)-1]
-           ticket = {'id':id, 'type':type, 'summary':summary, 'owner':owner, 'description': description, 'status':status, 'due_assign':due_assign_date, 'due_close':due_close_date, 'complete': complete, sorted_field: item}
+           ticket = {'id':id, 'type':type, 'summary':summary, 'owner':owner, 'description': description, 'status':status, 'due_assign':due_assign_date, 'due_close':due_close_date, 'complete': complete, sorted_field: item, 'due_assign_short':due_assign_date_short, 'due_close_short':due_close_date_short}
            self.log.debug(ticket)
            tickets.append(ticket)
 
@@ -233,6 +237,8 @@
                        'url':url, 'short_summary':t['summary'][0:10],
                        'assign':assign, 'todow':todow, 'latew':latew, 'complete':complete
                       })
+            # add for sort by assign date.
+            ts.sort(cmp=lambda x, y: cmp(x['assign'], y['assign']))
 
         req.hdf['gan'] = {
             'weekdays':[u"月", u"火", u"水", u"木", u"金", u"土", u"日"],
Index: ganttcalendar/templates/gantt.cs
===================================================================
--- ganttcalendar/templates/gantt.cs	(リビジョン 429)
+++ ganttcalendar/templates/gantt.cs	(作業コピー)
@@ -119,7 +119,7 @@
 }
 </style>
 
-<form>
+<form class="printableform">
 <table class="list">
   <tr>
     <td>
@@ -324,7 +324,18 @@
         <?cs if:t.complete > 0 && t.assign != -1 ?>
         <div style="top: <?cs var:offset * ti + 60 ?>px; left: <?cs var:t.assign * dw ?>px; width: <?cs var:t.complete ?>px;" class="ticket ticket_done"></div>
         <?cs /if ?>
-
+        <!-- add for showing ticket title on gantt bar -->
+        <div class="ticket" style="position:absolute; height:8px; top: <?cs var:offset * ti + 48 ?>px; left: <?cs if:t.assign * dw < 0 ?>3<?cs else ?><?cs var:t.assign * dw ?><?cs /if ?>px; width: 100%; font-size:10px; color:black">
+            <span class="tip">
+              <pre><span class="type"><?cs var:t.ticket['type'] ?></span>#<?cs var:t.ticket['id'] ?>: <?cs var:t.ticket['summary'] ?></pre>
+              <strong>担当者</strong>: <?cs var:t.ticket['owner'] ?><br />
+              <strong>開始日</strong>: <?cs var:t.ticket['due_assign'] ?><br />
+              <strong>終了日</strong>: <?cs var:t.ticket['due_close'] ?><br />
+              <strong>達成率</strong>: <?cs var:t.ticket['complete'] ?>%<br />
+              <strong>詳細</strong>: <pre><?cs var:t.ticket['description'] ?></pre>
+            </span>
+            <a href="<?cs var:t.url ?>" target="_blank">#<?cs var:t.ticket['id'] ?>:<?cs var:t.ticket['summary'] ?> (<?cs var:t.ticket['due_assign_short'] ?> - <?cs var:t.ticket['due_close_short'] ?>)</a>
+        </div>
 <?cs set:offset = offset + 1 ?>
 
       </span>

パッチをソースに適用しつつ、次のような感じでやります

cd <チェックアウト先の0.10ディレクトリ>
patch -p0 < gantt.patch
python setup.py bdist_egg
cp -f dist/TracGanttCalendarPlugin-0.0.1-py2.4.egg /usr/share/trac/plugins/
service httpd restart

Windows環境(Trac Lighting)なら、
http://cetus.sakura.ne.jp/softlab/toolbox1/index.html#difpat
とかのpatchツールを使うといいかもしれません。
また、
C:\TracLight\python\share\trac\plugins
あたりに、出来上がったeggファイルをコピーすればよいです。

ついでに

レポート用のSQLです。
マイルストーン別に 開始日か終了日が設定されていないチケットをリストアップします

SELECT p.value AS __color__,
  milestone AS __group__,
  id AS ticket, summary, status, assigned.value AS 開始日, closed.value AS 終了日, t.type AS type,
  (CASE status WHEN 'assigned' THEN owner||' *' ELSE owner END) AS owner,
  time AS created

 FROM
   ticket t
   LEFT JOIN enum p
     ON p.name = t.priority AND p.type = 'priority'

  LEFT JOIN
   (select ticket, name, value from ticket_custom where name = 'due_assign' ) assigned
    ON t.id = assigned.ticket
  LEFT JOIN
   (select ticket, name, value from ticket_custom where name = 'due_close' ) closed
    ON t.id = closed.ticket

 WHERE
   (assigned.value IS NULL)
     OR
   (assigned.value = '')
     OR
   (closed.value IS NULL)
     OR
   (closed.value = '')

 ORDER BY (milestone IS NULL),milestone, t.id

運用

私は次のように使っています。

  1. 要件定義書から、大きな粒度のチケットを切り出す。要件定義書のすべての要件を出し切る。
  2. 要件単位に大まかなスケジュールを組む
    • 開始日と終了日が空っぽのものを上記レポート用SQLで一覧にして、その一覧から大まかなスケジュールをいったん作ってしまいます。
  3. これでガントチャートにチケットが表示されるようになるので、スケジュールの調整をガントを見ながら行う
  4. 案ができたら、ガントをプリントなりプロジェクタに写すなりして確認会を行う

android sdk 1.5r3 で方位によって回転するMapを作る

HT-03Aを購入して、4週間ぐらいになりますが、楽しいですね。これ。
この間旅行に行ったとき、GoogleMapを使って、すごく便利だったので、地図関係のアプリを作ろうといろいろやっています。

そういうわけで電子コンパスの情報にあわせて地図の向きが変わる簡単なアプリを作りました。
iPhoneを持っている人に見せてもらった、こういうのです。
http://www.apple.com/jp/iphone/iphone-3gs/maps-compass.html

マーケットでは、AndNavというのがすでにあるのですが、自分で作りたかったので、どうやったら作れるのかGoogle先生でいろいろ調べてみてもなかなかいい情報が無くて、結構迷いました。

http://www.anddev.org/viewtopic.php?p=15173
で、AndNavの作者さんが、MapView#onDraw() をOverrideすれば良いと書いていましたが、1.5r3ではfinalで宣言されているので上書きできない状態です。以前はそれでよかったようですが、SDKのバージョンがあがってできなくなっているようですね。
ほかには、ViewGroup#onDispatchDrawをOverrideするという情報もありましたが、これは自分は動作させられなかったです。

試行錯誤の結果、MapView#draw() をOverrideすることで、実現することができたので、書いてみようと思います。

仕組みの概要

  1. MapViewのサブクラスを定義して、drawメソッドをオーバーライドする。
  2. オーバーライドしたdrawメソッド内で、canvas#rotate()する。rotateする量は、電子コンパスの値を元にする
  3. 電子コンパスの値を取得できるようにし、値が通知された際に、MapView#invalidate()する
  4. invalidateによって、再描画(?)が発生し、drawメソッドが呼ばれ、電子コンパスの値の逆方向に地図を回転させる
    • Logを仕掛けてみると、invalidateせずともdrawは頻繁に呼ばれます。ただ、スムーズに再描画させるなために強制的にinvalidateしています

地図の回転とは別に、GPSの位置情報を検出して、マップを移動する処理も入れてあります。

実際に端末上で動作させると次のような動きになります。


adakodaさんの Android Screen Monitor を使わせていただきました。 http://www.adakoda.com/adakoda/android/asm/

コード

2009/08/13 追記

216行目(コードの一番下のほうの行)でCanvas#scale()の第3、第4引数を本来とは逆に指定していました。このため、地図の回転軸がずれて見える結果になっていました。この点を修正しています。

  • 誤り
canvas.scale(scaleFactor, scaleFactor, centerH, centerW);
  • 正しい
canvas.scale(scaleFactor, scaleFactor, centerW, centerH);
package tomo.snowbug;

import android.content.Context;
import android.graphics.Canvas;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;

import com.google.android.maps.GeoPoint;
import com.google.android.maps.MapActivity;
import com.google.android.maps.MapController;
import com.google.android.maps.MapView;

public class CompasMapActivity extends MapActivity implements LocationListener,
		SensorEventListener {

	private String TAG;// LOG用のタグ名

	private MapController mapc;

	private MapView mapv;

	private SensorManager sensorMgr;

	// TYPE_ORIENTATION の sensor
	private Sensor sensor_orientation;

	// TYPE_ORIENTATION の sensorが検知した値
	private float[] orientationValues;

	/** Called when the activity is first created. */
	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);

		prepareSensors();
		registerSensors();

		TAG = getResources().getString(R.string.app_name);
		// res/values/strings.xmlに登録されているapp_nameを参照している

		setContentView(R.layout.main);

		// 現在位置の変化を検知する。
		LocationManager l = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
		l.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, this);

		mapv = new RotatableMapView(this, getString(R.string.map_key));

		mapc = mapv.getController();
		mapc.setZoom(15);
		mapv.setClickable(true);
		mapc.setCenter(new GeoPoint(35577737, 139736009));
		mapv.setBuiltInZoomControls(true);
		mapv.setReticleDrawMode(MapView.ReticleDrawMode.DRAW_RETICLE_OVER);

		setContentView(mapv);
	}

	/**
	 * GPSでの測位が変化した際に呼ばれる
	 */
	@Override
	public void onLocationChanged(Location location) {
		GeoPoint gp = new GeoPoint((int) (location.getLatitude() * 1E6),
				(int) (location.getLongitude() * 1E6));
		mapc.animateTo(gp);
	}

	@Override
	public void onProviderDisabled(String provider) {
		// TODO Auto-generated method stub
	}

	@Override
	public void onProviderEnabled(String provider) {
		// TODO Auto-generated method stub
	}

	@Override
	public void onStatusChanged(String provider, int status, Bundle extras) {
		// TODO Auto-generated method stub
	}

	@Override
	public boolean onCreateOptionsMenu(Menu m) {
		m.add(0, 1, 1, "home");
		m.add(0, 1, 2, "finish");
		return true;
	}

	@Override
	public boolean onPrepareOptionsMenu(Menu m) {
		return true;
	}

	@Override
	public boolean onOptionsItemSelected(MenuItem mItem) {

		if ("home".equals(mItem.getTitle())) {
			mapc.animateTo(new GeoPoint(35577737, 139736009));
		}
		if ("finish".equals(mItem.getTitle())) {
			finishActivity();
		}
		return true;
	}

	@Override
	public boolean onSearchRequested() {
		super.onSearchRequested();
		startSearch(null, false, null, true);
		return true;
	}

	@Override
	protected boolean isRouteDisplayed() {
		// TODO Auto-generated method stub
		return false;
	}

	@Override
	protected void onResume() {
		super.onResume();
		registerSensors();
	}

	@Override
	protected void onPause() {
		super.onPause();
		sensorMgr.unregisterListener(this);
	}

	@Override
	protected void onStop() {
		// TODO Auto-generated method stub
		super.onStop();
		finishActivity();
	}

	/**
	 * 加速度センサー変化時に呼ばれる
	 */
	@Override
	public void onAccuracyChanged(Sensor sensor, int accuracy) {
		// TODO Auto-generated method stub
	}

	/**
	 * 傾きセンサーの変化時に呼ばれる
	 */
	@Override
	public void onSensorChanged(SensorEvent event) {
		orientationValues = event.values;
		Log.d(TAG, "mValues[" + orientationValues[0] + ", "
				+ orientationValues[1] + ", " + orientationValues[2]);
		mapv.invalidate(); // 再描画のため(MapViewのdrawが呼ばれる)
	}

	// ------------------------------------------

	private void registerSensors() {
		sensorMgr.registerListener(this, sensor_orientation,
				SensorManager.SENSOR_DELAY_UI);
		// DELAYをUIにしないと頻繁に書き換えすぎるようです
	}

	private void prepareSensors() {
		sensorMgr = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
		sensor_orientation = sensorMgr.getSensorList(Sensor.TYPE_ORIENTATION)
				.get(0);
	}

	private void finishActivity() {
		sensorMgr.unregisterListener(this);
		this.finish();
	}

	/**
	 * 回転可能なMapView
	 */
	private class RotatableMapView extends MapView {

		public RotatableMapView(Context context, String apiKey) {
			super(context, apiKey);
		}

		@Override
		public void draw(Canvas canvas) {

			if (orientationValues == null) {
				super.draw(canvas);
				return;
			}
			Log.d(TAG, "draw!!!!! ");
			int h = getHeight();
			int w = getWidth();
			float centerH = h / 2;
			float centerW = w / 2;

			// あらかじめMapView.invalidate()されていることが前提
			// 再描画なので、方角とは逆にCanvasを回転させれば良い
			canvas.rotate(-orientationValues[0], centerW, centerH);

			// Canvasの大きさを拡大することで、Canvasを回転した際に隅に現れる、地図が描画されない真っ黒な領域を見せないようにしている
			final float scaleFactor = (float) (Math.sqrt(h * h + w * w) / Math
					.max(w, h));
			canvas.scale(scaleFactor, scaleFactor, centerW, centerH);
			super.draw(canvas);
		}
	}
}

課題

  1. 滑らかに地図を回転させる(ローパスフィルターのような)
  2. 地図の移動が正しくできるようにする
  3. 中心にアイコンを設置して、そのアイコンが必ず端末の上方向を向くようにする
  4. 端末が向いている方向を文字で表示する。