tech::hexagram

personal note for technical issue.

GPSデータをリアルタイムで取ってきてファイルに保存する

久々の開発日記の更新.

研究と絡むのですが,AndroidGPSのデータを取ってきてそれをファイルに保存するなんてことを最近やってました.(結果的には自分のAndroidでは何らかの不具合によりうまくいかなかったですが)

若干長いので,ソースコードを上から分割して解説.

java

package jp.android.testgpslogger;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
import java.util.*;

import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.*;
//GPS
import android.location.Criteria;
import android.location.Location;
import android.location.LocationManager;
import android.location.LocationProvider;
import android.location.LocationListener;
import android.location.GpsStatus;
//File
import android.os.*;

このへんがインポート部分.GPSに関わるのはLocationListenerとかCriteriaとかですね.

public class TestGPSLogger extends Activity implements LocationListener,GpsStatus.Listener{
    
    private LocationManager locationmanager;
    TextView textview_location,textview_date,textview_state;
    String file_date,file_path;
    int state=0;
    public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);
    
    //Button
    Button button_log_start=(Button)findViewById(R.id.button_log_start);
    Button button_log_end=(Button)findViewById(R.id.button_log_end);
    //GPS Initialize
    locationmanager=(LocationManager)getSystemService(LOCATION_SERVICE);
    locationmanager.addGpsStatusListener(this);

この辺は初期設定とかです.今回,ボタンを2つ用意し,button_log_startを押すとloggingを開始し,button_log_endを押すとloggingを止める仕様にしてみました.

//Button OnClickListener
    button_log_start.setOnClickListener(new View.OnClickListener() {
    public void onClick(View v) {
    //GPS Start
    if(locationmanager!=null){
    
    
    Criteria criteria=new Criteria();
    criteria.setAccuracy(Criteria.ACCURACY_COARSE);
    criteria.setPowerRequirement(Criteria.POWER_HIGH);
    criteria.setSpeedRequired(false);
    criteria.setAltitudeRequired(false);
    criteria.setBearingRequired(false);
    criteria.setCostAllowed(false);
    String provider=locationmanager.getBestProvider(criteria, true);
    locationmanager.requestLocationUpdates(provider, 0,0,TestGPSLogger.this);
    //File Open
    Calendar calendar=Calendar.getInstance();
    int year=calendar.get(Calendar.YEAR);
    int month=calendar.get(Calendar.MONTH);
    int day=calendar.get(Calendar.DAY_OF_MONTH);
    int hour=calendar.get(Calendar.HOUR_OF_DAY);
    int minute=calendar.get(Calendar.MINUTE);
    int second=calendar.get(Calendar.SECOND);
    file_date=year+"-"+month+"-"+day+"-"+hour+"-"+minute+"-"+second;
    File sdhome=Environment.getExternalStorageDirectory();
    if(sdhome.exists() && sdhome.canWrite()){
        File file_dir=new File(sdhome.getAbsolutePath()+"/GPSLogger/");
        file_dir.mkdir();
    }
    
    file_path=Environment.getExternalStorageDirectory()+"/GPSLogger/"+file_date+".dat";
    if(state==0){
        try{
        BufferedWriter bw=new BufferedWriter(
        new OutputStreamWriter(
            new FileOutputStream(file_path,true),"UTF-8"));
        String title="latitude longitude accuracy\n";
        bw.write(title);
        bw.close();
        state=1;
        Toast.makeText(TestGPSLogger.this, "GPS logging started.", Toast.LENGTH_SHORT).show();
        } catch (Exception e){}
    }
    }
    
    }
});

この部分,長いですがbutton_log_startのボタンを押したときの挙動を書いています.
Androidのプログラムを書く際は最初にextends Activityを入れるのがお約束ですが,GPSを使う場合は更にimplements LocationListenerを入れる必要があります.

次に,CriteriaによりGPS取得のための要求仕様を色々と設定しています.バッテリ消費量の許容度や,速度・加速度の取得有無などの設定を行えます.

locationmanager.requestLocationUpdates(provider, 0,0,TestGPSLogger.this);

LocationListenerが,位置の更新があった際にupdateを行うようにコールバック関数を設定してるのがこちらのrequestLocationUpdatesです.
第二引数が通知時間の間隔(ミリ秒),第三引数が通知距離の間隔(メートル)です.ここでは変化したらすぐに呼ばれるように設定してあります.

そして,ファイル出力の設定を行っています.ここでは,日時を取得しYear-Month-Date-Hour-Minute-Second.datというファイルに保存しました.microSDカードに保存するように設定しています.
今回記録する要件としては,latitude(緯度)・longitude(経度)・accuracy(取得精度)の3種類のデータです.

ファイル書き込みのテストとして,文字列「latitude longitude accuracy」を1行目に書いてみています.

        BufferedWriter bw=new BufferedWriter(
        new OutputStreamWriter(
            new FileOutputStream(file_path,true),"UTF-8"));

このFileOutputStreamの部分の第二引数をtrueにすると追記モードでファイルの書き込みが行われます.falseにすると上書きモードですので,随時更新する場合はtrueを選択します.

    button_log_end.setOnClickListener(new View.OnClickListener() {
    public void onClick(View v) {
    if(locationmanager!=null){
    locationmanager.removeUpdates(TestGPSLogger.this);
    locationmanager.removeGpsStatusListener(TestGPSLogger.this);
    state=0;
    Toast.makeText(TestGPSLogger.this,"GPS logging ended.",Toast.LENGTH_SHORT).show();
    }
    }
    });
    
    }

こちらはbutton_log_endを押したときの挙動です.ログ取りを終了し,GPS周りを停止させています.

    public void onLocationChanged(Location location) {
    //location
    textview_location=(TextView)findViewById(R.id.textview_location);
    String location_string="Latitude:";
    location_string+=Double.toString(location.getLatitude())+"\n";
    location_string+="Longitude:";
    location_string+=Double.toString(location.getLongitude())+"\n";
    location_string+="Accuracy:";
    location_string+=Double.toString(location.getAccuracy())+"\n";
    textview_location.setText(location_string);
    //last updates
    textview_date=(TextView)findViewById(R.id.textview_date);
    Date date=new Date(location.getTime());
    String date_string="Last updated:"+date.toString();
    textview_date.setText(date_string);
    //write file
    if(state==1){
    try{
    BufferedWriter bw=new BufferedWriter(
        new OutputStreamWriter(
        new FileOutputStream(file_path,true),"UTF-8"));
    String file_string=Double.toString(location.getLatitude())+" "+Double.toString(location.getLongitude())+" "+Double.toString(location.getAccuracy())+"\n";
    bw.write(file_string);
    bw.flush();
    bw.close();
    } catch (Exception e){}
    }
    }

この部分は位置が変化したときの挙動を示しています.textViewに変化後の緯度・経度・精度を表示し,それと同時にファイルへの位置の追記を行っています.

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

この部分はimplements LocationListenerを呼び出したときに必須となる関数群です.上からそれぞれ,LocationProviderがOFFになったとき/ONになったとき,GPSの利用状態が変化したときに呼ばれます.

    public void onDestroy(){
    super.onDestroy();
      if(locationmanager!=null){
      locationmanager.removeUpdates(TestGPSLogger.this);
      locationmanager.removeGpsStatusListener(TestGPSLogger.this);
      }
    }
}

プログラムがバックグラウンドになった際にバッテリ節約のためGPSを切るようにしています.

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:text=""
    android:id="@+id/textview_title"
    />
    <LinearLayout android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:gravity="center">
	    <Button android:layout_width="wrap_content"
    	android:layout_height="wrap_content"
    	android:text="Logging start"
    	android:id="@+id/button_log_start"/>
    	<Button android:layout_width="wrap_content"
    	android:layout_height="wrap_content"
    	android:text="Logging end"
    	android:id="@+id/button_log_end"/>
    	<TextView android:layout_width="fill_parent"
    	android:layout_height="wrap_content"
	    android:id="@+id/textview_location"/>
	    <TextView android:layout_width="fill_parent"
	    android:layout_height="wrap_content"
	    android:id="@+id/textview_date"/>
	    <TextView android:layout_width="fill_parent"
	    android:layout_height="wrap_content"
	    android:id="@+id/textview_state"/>
    </LinearLayout>
</LinearLayout>

こちらは至ってシンプルです.ボタンを2つセットし,TextViewを3つ(緯度・経度・精度の情報&取得日時&GPSの利用状態)を書くためのフィールドをセットしています.