0

json_buiilderをgithubに移動させた

bitbucketからhttp://github.com/shokai/json_builder-cppに移動した。

ちょっとまじめにREADMEを書いたりした。
githubのreadmeにはmarkdown形式を使った。markdownをインストールすればローカルでもマークアップがプレビューできて良い

json_builder.hはヘッダファイル単体でstd::mapやvectorをjsonにシリアライズするC++用のライブラリ。parseはできない。

0

Android SDKのlogcat

Log.v("button1", "押した");

などするとAndroid実機からEclipseにテキストが流れてくるlogcatだけど
17インチMacbookProでは文字が超小さい。なのにフォント設定ができなかったり、日本語が文字化けしたりする。



terminalで
adb logcat
で起動する方のlogcatならちゃんと日本語出る。

少なくともソースコードUTF-8で、iTermもUTF-8で使ってるので文字化けしない。


adbは複数同時に起動できるので、screenで画面分割して2つ起動。
左側は
adb logcat | grep "^V\/"
でLog.vでの出力だけを表示するようにしてみたりとかすると、Eclipseよりこっちの方が便利な気がしてくる。
adb logcat

2

AndroidでGPSロガー

Android2.1でGoogle Map API、メニュー画面、GPSでの位置の計測を使う方法が理解できたので作った。
GPSとって5秒おきにアップデートし、Google Mapに自分の位置を表示する。
移動に合わせて地図の中心点を動かし、移動のログを地図上に赤線で表示する。

GPS使いまくったら、電車で藤沢と日吉の間を往復する間使い続けただけでHTC Desireの電池が40%ぐらい減った。5秒間隔は狭すぎるか。

バイナリはAndroid2.1以上向け。右クリックで保存し.apkにリネームすればインストールできる

昨日テストした。日吉から藤沢方面に戻るところ。
緯度と経度のListをメモリ上に保存してあるので、線を引いて示す事ができる。
メモリ上に保存してあるだけなのでアプリを終了すると消える。でもAndroidアプリはそもそも終了しないので電源を切るまでは残る。そのうちSDカードに保存するようにしよう
R0014774.JPG


Menuボタンを押すと
  • GPSのon/off、最後に計測した場所に戻る
  • ズームをレベル18にする(18ぐらいが人間が徒歩で使う地図として調度良い)
  • 衛星写真と地図の切り替え
  • ログ線の表示on/off
  • ができる。
    GPS Tracker


    電車で日吉駅に入る所
    R0014768.JPG


    電車で二俣川あたりを通り過ぎる
    GPS Tracker



    ■GPSで位置情報を取る
    橋本商会 適当なLocationProviderから位置情報を取るに書いたうち、requestLocationUpdatesを使った。


    GPSまわりは、Android2.1と1.6以前でAPIがかなり違う。
    ネットで検索して2009年春以前の記事を見てやってたら痛い目を見た。
    出たばかりのこの本は全体的に薄く広くカバーしていて、とっかかりとして良い。ホームスクリーンウィジェットやbluetoothまで一応載っている。
    レイアウトにXMLを使わず全てJavaで書いているけど。
    Android2.1プログラミングバイブル
    布留川 英一
    ソシム
    売り上げランキング: 847


    ■メニューボタン
    Activityを継承しているクラスならそのまま@OverrideでonCreateOptionsMenuとonInputItemSelectedを実装すれば使える。
    こんな風にクラス内クラスを作っておいて
    private static class MenuId{
        private static final int START_GPS = 1;
        private static final int LAST_LOCATION = 2;
        private static final int SET_ZOOM = 3;
        private static final int SATELLITE_TOGGLE = 4;
        private static final int LOG_TOGGLE = 5;
    }


    ボタンを追加して
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
          boolean supRetVal = super.onCreateOptionsMenu(menu);
          menu.add(0, MenuId.START_GPS, 0, "Start GPS");
          menu.add(0, MenuId.LAST_LOCATION, 0, "Last Location");
          return supRetVal;
    }


    アイテムが選択された時のイベント内でどのボタンが押されたかを判定する。
    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case MenuId.START_GPS:
            // 何か処理
                break;
            case MenuId.LAST_LOCATION:
            // 何か処理
                break;
        }
    }
    イベント内でitem.setTitle()を使えばボタンの文字列を変更できる。


    ■Google Mapを表示する
    mapの表示の前に準備がいる。
    1. Eclipseで[Window]→[Android SDK and AVD Manager]→updateでGoogle APIをインストールする
    2. projectのPropertiesからbuild targetを変更。Android 2.1からGoogle APIのAndroid 2.1にする
    3. 最初に作ったActivityをcom.google.maps.MapActivityの継承に変更
    4. Google Maps API Keyを取得


    Google Maps API Keyの取得に必要なMD5 fingerprintは、Macなら
    keytool -list -keystore ~/.android/debug.keystore
    して適当なパスワードを入れたら生成された。~/.androidディレクトリごと生成された。


    AndroidManifest.xmlにandroid.permission.INTERNETとcom.google.android.mapsを追加する
    <?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="org.shokai.gpstracker"
          android:versionCode="1"
          android:versionName="1.0">
        <application android:icon="@drawable/icon" android:label="@string/app_name">
            <activity android:name=".GpsTracker"
                      android:label="@string/app_name"
                      android:screenOrientation="portrait">
                <intent-filter>
                    <action android:name="android.intent.action.MAIN" />
                    <category android:name="android.intent.category.LAUNCHER" />
                </intent-filter>
            </activity>
    <uses-library android:name="com.google.android.maps" />
        </application>

    <uses-permission android:name="android.permission.INTERNET"/>
        <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
        <uses-permission android:name="android.permission.ACCESS_MOCK_LOCATION"/>    

    </manifest>

    res/layout/main.xmlにAPI key取得時にでてきたサンプルコードを貼り付けると、MapViewが表示される。
    <com.google.android.maps.MapView
    android:id="@+id/mapview"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:enabled="true"
    android:clickable="true"
    android:apiKey="your-api-key"
    />
    clickableやenabledをtrueにした。地図を指でつまんで動かせるようになる。


    main.xmlを書き換えたら、R.javaにいつのまにかMapViewのidが入っていた。
    import com.google.android.maps.*;
    MapView map = (MapView)findViewById(R.id.mapview);
    という風にcontrollerから扱える。


    これでとりあえず地図は表示できる。


    ■地図を操作する
    onLocationChangedでLocationオブジェクトが取れるので、そこから緯度経度が取れる。
    MapViewからMapControllerを取得して操作する。
    double lat = location.getLatitude(); // 緯度
    double lon = location.getLongitude(); // 軽度
    MapController mc = map.getController();
    GeoPoint p = new GeoPoint((int)(lat*1E6), (int)(lon*1E6));
    mc.setCenter(p);
    mc.setZoom(18);



    ■Google Map上にOverlayを表示する
    自分の位置を表示するだけなら、地図/位置情報/GPSを使うAndroidアプリを作るには (1/3) – @ITのようにMyLocationOverlayを使うといい。

    ただこの通りやるとGPSの取得間隔や精度を詳細に指定できるrequestLocationUpdateの機能とぶつかるのでそのままは使わず、onLocationChangedの中で位置を指定するようにした。


    とりあえずMyLoactionOverlayを作ってmapに配置しておくが、表示はしない
    MapView map = (MapView)findViewById(R.id.mapview);
    MyLocationOverlay myOverlay = new MyLocationOverlay(getApplicationContext(), map);
    myOverlay.onProviderEnabled(LocationManager.GPS_PROVIDER);
    map.getOverlays().add(myOverlay);

    自分アイコンを地図に表示
    public void onLocationChanged(Location location) {
        myOverlay.getMyLocation();
    }



    ログの線の描画は、Overlayを継承したLogOverlayクラスを自分で作り、draw関数の中を実装してやると作れる。drawはどうやらMapViewが描画しなおす毎に実行されるっぽい。MapView.invalidate()を呼ぶとすぐ再描画できる。
    Google MapにDrawableを配置する – Android Wiki*に東京と大阪の間を緯度経度で指定して線で結ぶ例があったので参考にした。

    LogOverlay.java
    package org.shokai.gpstracker;

    import java.util.*;
    import android.graphics.*;
    import android.graphics.Paint.*;
    import com.google.android.maps.*;

    public class LogOverlay extends Overlay {

    private Paint linePaint;
    private List<GeoPoint> points;

    public LogOverlay() {
    this.points = new ArrayList<GeoPoint>();
    this.linePaint = new Paint();
    linePaint.setARGB(255, 255, 0, 0);
    linePaint.setStrokeWidth(2);
    linePaint.setDither(true);
    linePaint.setStyle(Style.FILL);
    linePaint.setAntiAlias(true);
    linePaint.setStrokeJoin(Paint.Join.ROUND);
    linePaint.setStrokeCap(Paint.Cap.ROUND);
    }

    public void add(GeoPoint p){
    points.add(p);
    }

    @Override
    public void draw(Canvas canvas, MapView view, boolean shadow){
    if(points.size() < 2) return;
    Point p_a = new Point();
    Point p_b = new Point();
    for(int i = 0; i < points.size()-1; i++){
    view.getProjection().toPixels(points.get(i), p_a);
    view.getProjection().toPixels(points.get(i+1), p_b);
    canvas.drawLine(p_a.x, p_a.y, p_b.x, p_b.y, linePaint);
    }
    }

    public int size(){
    return points.size();
    }

    public List<GeoPoint> getPoints(){
    return this.points;
    }

    }



    ■画面を回転させない
    Androidは画面を回転させると、onDestroyイベント→onCreateイベントの順にイベントが発生して画面がまるごと描画しなおされてしまう。
    なので、画面を回転させるだけでTextViewは中身が消滅するし、Google MapにOverlayとして描画していた画像も消える。
    値や描画はいいんだけど、requestLocationUpdateの取り消しができなくなって、アプリを強制終了するまでGPSマークが点きっぱなしになったりするのがまずい。


    しょうがないので画面を回転させないようにした。


    回転させない方法としては八角研究所 : Android で再開する Java プログラミング(12) – Android ライフサイクルを考慮-手書きメモを作り込む
    に書いてある様にAndroidManifest.xmlの manifest/application/activityのattributeとして
    android:screenOrientation="portrait"
    を書いておくと縦画面専用になり、回転しなくなる。
    landscapeだと横になる。



    理想的な方法としては、回転した後に値を復元するのがある。
    画面回転時の挙動 – isherの日記が参考になる。
    onSaveInstanceState と onRestoreInstanceStateを@Overrideで実装してBundleにputString/getStringして復元する。
        @Override
        protected void onSaveInstanceState(Bundle bundle) {
            super.onSaveInstanceState(bundle);        
            bundle.putString("textViewMessage", this.textViewMessage.getText().toString() );
        }

        @Override
        protected void onRestoreInstanceState(Bundle bundle) {
            super.onRestoreInstanceState(bundle);
            this.textViewMessage.setText(bundle.getString("textViewMessage"));
        }
    key-valueでbundleに保存して復元できる。


    値の保存と復元ならこれでいいんだけど、GPSがどうしょうもない。センサーまわりのAPIの癖をつかまないと画面回転ができないアプリになっちゃって困る。


    GpsTracker.java
    package org.shokai.gpstracker;

    import android.os.Bundle;
    import com.google.android.maps.*;

    import android.content.Context;
    import android.location.*;
    import android.view.*;
    import android.widget.*;
    import java.util.*;

    public class GpsTracker extends MapActivity implements LocationListener{

    private MapView map;
    private TextView textViewMessage;
    private LocationManager lm;
    private MyLocationOverlay myOverlay;
    private final int zoom_default = 18;
    private boolean location_enalbed, log_enabled; // GPSとコンパスを動かしているかどうか、logを表示しているかどうか
    private LogOverlay logOverlay;

    private static class MenuId{
         private static final int START_GPS = 1;
         private static final int LAST_LOCATION = 2;
         private static final int SET_ZOOM = 3;
         private static final int SATELLITE_TOGGLE = 4;
         private static final int LOG_TOGGLE = 5;
    }

    public GpsTracker(){
            this.location_enalbed = false;
    }

        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.main);
            this.textViewMessage = (TextView)findViewById(R.id.textViewMessage);
            lm = (LocationManager)this.getSystemService(Context.LOCATION_SERVICE);
            map = (MapView)findViewById(R.id.mapview);
            myOverlay = new MyLocationOverlay(getApplicationContext(), map);
            myOverlay.onProviderEnabled(LocationManager.GPS_PROVIDER);
            map.getOverlays().add(myOverlay);
    logOverlay = new LogOverlay();
        }

        @Override
        public boolean onCreateOptionsMenu(Menu menu) {
          boolean supRetVal = super.onCreateOptionsMenu(menu);
          menu.add(0, MenuId.START_GPS, 0, "Start GPS");
          menu.add(0, MenuId.LAST_LOCATION, 0, "Last Location");
          menu.add(0, MenuId.SET_ZOOM, 0, "Zoom");
          menu.add(0, MenuId.SATELLITE_TOGGLE, 0, "Satellite/Map");
          menu.add(0, MenuId.LOG_TOGGLE, 0, "Show Logs");
          return supRetVal;
        }
        
        @Override
        public boolean onOptionsItemSelected(MenuItem item) {
         switch (item.getItemId()) {
    case MenuId.START_GPS:
    if(!this.location_enalbed){
    lm.requestLocationUpdates(LocationManager.GPS_PROVIDER, 5000, 10, this); // 5(sec), 10(meter)
            myOverlay.enableMyLocation();
            myOverlay.enableCompass();
            message("Start GPS");
            this.location_enalbed = true;
            item.setTitle("Stop GPS");
    }
    else{
    lm.removeUpdates(this);
    myOverlay.disableCompass();
    myOverlay.disableMyLocation();
    message("Stop GPS");
    this.location_enalbed = false;
    item.setTitle("Start GPS");
    }
    break;
    case MenuId.LAST_LOCATION:
    Location loc = lm.getLastKnownLocation(LocationManager.GPS_PROVIDER);
    double lat = loc.getLatitude();
    double lon = loc.getLongitude();
    message("last lat:"+Double.toString(lat) + ", lon:" + Double.toString(lon));
    this.setPosition(lat, lon, this.zoom_default);
    break;
    case MenuId.SET_ZOOM:
    MapController mc = map.getController();
    mc.setZoom(this.zoom_default);
    map.getOverlays().add(logOverlay);
    break;
    case MenuId.SATELLITE_TOGGLE:
    if(map.isSatellite()){
    map.setSatellite(false);
    }
    else{
    map.setSatellite(true);
    }
    break;
    case MenuId.LOG_TOGGLE:
    if(this.log_enabled != true){
    map.getOverlays().add(logOverlay);
    item.setTitle("Hide Logs");
    message("logs: "+Integer.toString(logOverlay.size()));
    log_enabled = true;
    }
    else{
    map.getOverlays().remove(logOverlay);
    item.setTitle("Show Logs");
    log_enabled = false;
    }
    map.invalidate(); // すぐ再描画
    break;
         }
         return true;
        }
        
        public void setPosition(double lat, double lon, int zoom){
         MapController mc = map.getController();
         GeoPoint p = new GeoPoint((int)(lat*1E6), (int)(lon*1E6));
         logOverlay.add(p);
         mc.setCenter(p);
         mc.setZoom(zoom);
         this.myOverlay.getMyLocation();
        }

        // zoomは変更せずに地図だけ動かす
        public void setPosition(double lat, double lon){
         this.setPosition(lat, lon, map.getZoomLevel());
        }
        
    public void message(String mes){
    this.textViewMessage.setText(mes);
    }

    public void onLocationChanged(Location location) {
            double lat = location.getLatitude();
            double lon = location.getLongitude();
    message("lat:"+Double.toString(lat)+", lon:"+Double.toString(lon));
    this.setPosition(lat, lon);
    }

    public void onProviderDisabled(String provider) {
    }

    public void onProviderEnabled(String provider) {
    }

    public void onStatusChanged(String provider, int status, Bundle extras) {
    }

    @Override
    protected boolean isRouteDisplayed() {
    return false;
    }

        @Override
        protected void onSaveInstanceState(Bundle bundle) {
            super.onSaveInstanceState(bundle);        
            bundle.putString("textViewMessage", this.textViewMessage.getText().toString() );
        }

        @Override
        protected void onRestoreInstanceState(Bundle bundle) {
            super.onRestoreInstanceState(bundle);
            this.textViewMessage.setText(bundle.getString("textViewMessage"));
        }
        
    }



    res/layout/main.xml
    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:orientation="vertical"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        >

    <TextView  
        android:layout_width="fill_parent" 
        android:layout_height="wrap_content" 
        android:id="@+id/textViewMessage" android:text="@string/message"/>
            
    <com.google.android.maps.MapView
    android:id="@+id/mapview"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:enabled="true"
    android:clickable="true"
    android:apiKey="0y0rb2n5OYga60X7toG-HzSYQh2ICnOT27xI4LA"
    />


    </LinearLayout>


    AndroidManifest.xml

    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="org.shokai.gpstracker"
          android:versionCode="1"
          android:versionName="1.0">
        <application android:icon="@drawable/icon" android:label="@string/app_name">
            <activity android:name=".GpsTracker"
                      android:label="@string/app_name"
                      android:screenOrientation="portrait">
                <intent-filter>
                    <action android:name="android.intent.action.MAIN" />
                    <category android:name="android.intent.category.LAUNCHER" />
                </intent-filter>
            </activity>
    <uses-library android:name="com.google.android.maps" />
        </application>

    <uses-permission android:name="android.permission.INTERNET"/>
        <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
        <uses-permission android:name="android.permission.ACCESS_MOCK_LOCATION"/>    

    </manifest>


0

適当なLocationProviderから位置情報を取る

AndroidはGPS以外にもWiFiのESSIDやIPアドレスとかで位置情報がとれるので(機種によるし今後増減すると思うが)、bestなLocationProvierから緯度経度を取るのはこんな感じ

Android2.1向け。

LocationManager lm = (LocationManager)this.getSystemService(Context.LOCATION_SERVICE);

Criteria crit = new Criteria();
crit.setAccuracy(Criteria.ACCURACY_FINE);
String provider = lm.getBestProvider(crit, true);
Location loc = lm.getLastKnownLocation(provider);

double lat = loc.getLatitude();
double lon = loc.getLongitude();
参考:Using Location API – Marakana




ふつうにGPSを使って緯度経度を取るのは、衛星キャッチして計測するまで少し間が空くものなのでonLocationChangedイベントを登録して待とう


自らLocationListenerをimplementsして
public class GpsTracker extends Activity implements LocationListener{
 // code
}


requestLocationUpdatesを呼ぶ。画面上の方のメニューバーにGPS測位中アイコンが出るはず
LocationManager lm = (LocationManager)this.getSystemService(Context.LOCATION_SERVICE);
lm.requestLocationUpdates(LocationManager.GPS_PROVIDER, 5000, 10, this); // 5秒間隔、10メートル


位置がupdateされる毎にこの関数が呼ばれる
public void onLocationChanged(Location location) {
double lat = location.getLatitude();
double lon = location.getLongitude();
}

onLocationChangedが呼ばれるタイミングは、この場合5000ミリ秒経過した&&10メートル以上移動したという事でいいのかな?
よくわからない。0メートルにしたらほぼ5秒間隔で呼ばれた。


ちなみに複数回requestLocationUpdatesすると、リスナが多重に登録されてしまう。数を数えておくか、登録する前にあらかじめremoveしておくとかすれば良さそう
lm.removeUpdates(this);
removeUpdatesを呼ぶか、アプリ自体を終了させるまでGPSのアイコンは点きっぱなしになっていた。
ユーザが止めれるようにするか、数分で自動的に止めるべき。


あと当たり前だけどLocationManagerを初期化しているgetSystemService()はonCreateイベント内かそれより後で呼ばないとアプリごと強制終了する。コンストラクタで呼んだら堕ちた。

2

WordPressにFacebook like buttonプラグインをインストールした

FacebookのlikeボタンのWordPress用プラグインがあったのでインストールした。

Creating a WordPress plugin: Add the new Facebook Like button to your posts Bottomless, Endless


wget http://blog.bottomlessinc.com/wp-content/uploads/2010/04/like.zip
unzip like.zip
cp like/tt_like_widget.php /path/to/blog/wp-content/plugins/


でWordPressのダッシュボードからpluginを有効化して、設定する。

Facebook User IDはUser ID – Facebook Developer Wikiで自分の名前をクリックしたら表示された。

blogのcssがいけないのか、Show Facesチェックボックスを入れていても顔アイコンが表示されなかった。plugin設定ページでHeightを70にしたら表示された。