2013年3月16日土曜日

Xamarin で android の GPS NMEA メッセージを取得する

Xamarin で android の GPS 位置情報を利用 に少しコードを追加して、GPS の NMEA メッセージを取得してみました。
これをテキストファイルに保存するだけで、地図ソフトなんかで読み込める GPS のログファイルになります。フォーマットを変換して google アースで軌跡を表示させたりもできます。


まず、Xamarin で AndroidManifest.xml が見当たらないんだけど、どうするの? なんかを参考に、GPS のパーミッションを設定します。(ACCESS_FINE_LOCATION を許可)

アクティビティは、文字を表示するために TextView を 3個貼り付けています。NMEA メッセージを表示する TextView はスクロールさせたかったので、ScrollView の上に貼り付けました。

以下コード


using System;
using Android.App;
using Android.Content;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using Android.OS;
using Android.Locations;    // GPSを使うので追加
using System.Linq;          // クエリの処理に必要

namespace gps_nmea0316
{
    [Activity (Label = "gps_nmea0316", MainLauncher = true)]
    public class Activity1 : Activity, ILocationListener, GpsStatus.INmeaListener
    {
        private LocationManager _locationManager;
        private TextView text1;
        private TextView text2;
        private TextView text3;
        private string _locationProvider;

        protected override void OnCreate (Bundle bundle)
        {
            base.OnCreate (bundle);

            // Set our view from the "main" layout resource
            SetContentView (Resource.Layout.Main);

            text1 = FindViewById<TextView> (Resource.Id.textView1);
            text2 = FindViewById<TextView> (Resource.Id.textView2);
            text3 = FindViewById<TextView> (Resource.Id.textView3);
            InitializeLocationManager();            // GPS を使う準備(すぐ下)
        }

        // GPSを使う準備
        private void InitializeLocationManager()
        {
            _locationManager = (LocationManager) GetSystemService(LocationService);
            var criteriaForLocationService = new Criteria
            {
                Accuracy = Accuracy.Fine
                // Fine なら GPS 、Coarse なら ネットワークを使用して測位
            };

            _locationManager.AddNmeaListener (this);

            // criteriaForLocationService(すぐ上)を使いたいな とおねがい
            var acceptableLocationProviders
                = _locationManager.GetProviders(criteriaForLocationService, true);
            
            // ロケーションプロバイダが使えるかな?
            if (acceptableLocationProviders.Any())
            {
                // acceptableLocationProvidersに何かクエリが入っていたら
                // 最初のクエリを取り出す
                _locationProvider = acceptableLocationProviders.First();
            }
            else
            {
                // ロケーションプロバイダが使えないみたい
                _locationProvider = String.Empty;
                text1.Text="測位サービスが使えないみたい";
            }
        }

        protected override void OnResume()
        {
            base.OnResume();
            
            // アクティビティがレジュームされたら
            // 位置情報を更新してくれるように依頼する
            _locationManager.RequestLocationUpdates(_locationProvider, 0, 0, this);
            
            text1.Text = _locationProvider.ToUpper () + " 位置情報更新待ち";
        }
        
        protected override void OnPause()
        {
            base.OnPause();
            
            // アクティビティが停止されたら
            // 位置情報更新しないように依頼
            _locationManager.RemoveUpdates(this);
            _locationManager.RemoveNmeaListener (this);
        }

        // 位置情報が更新された時の処理
        public void OnLocationChanged(Location location)
        {
            // 何もしない
        }
        
        // ロケーションプロバイダが有効になった時の処理
        public void OnProviderEnabled(string provider) 
        {
            // 何もしない
        }
        
        // ロケーションプロバイダが無効になった時の処理
        public void OnProviderDisabled(string provider) 
        {
            // 何もしない
        }
        
        // ロケーションプロバイダのステータスが変わった時の処理
        public void OnStatusChanged(string provider, Availability status, Bundle extras)
        {
            // 表示してみる
            text1.Text=string.Format("{0}, {1}", provider, status);
        }

        // NMEA メッセージを受信した時の処理
        public void OnNmeaReceived(long time,string nmea)
        {
            // 表示する
            text1.Text=_locationProvider.ToUpper () + " 測位中";
            text2.Text = "TIME: " + time.ToString ();
            text3.Text = text3.Text.Insert (0, nmea);

            // 2000文字を超えた分を削除する
            // (メモリ節約)
            if (2000 < text3.Text.Length) {
                text3.Text.Remove(2000);
            }
        }
    }
}



実行してみた様子

位置情報が更新されなくても(有効な位置情報が無くても)時間のデータは更新されて、OnNmeaReceived イベントが発生するので、データを利用するときには注意する。OnLocationChanged イベントは有効な位置情報が得られてから発生するので、それを利用するとか、NMEA データの内容を解釈して判断するとか。

(この写真、位置データを取得する前の写真だった。ちょっと失敗。)