おはやし日記

テーマ……バイク←プログラミング←旅

FIT形式のファイルをスマホでCSVに変換する-1

こんにちは。バイクで走ってる時の走行ログをとるのに中華サイコンを買いました。

o-treetree.hatenablog.com

↑この記事でも使っているのですが、データ処理をスマホ上で完結させるためにアプリを作ったので色々メモしておきます。

FITファイルとは

Garminのデバイス等で採用されている、スポーツ・フィットネスデバイスの情報共有プロトコル?です。詳しくは以下を見てください。

developer.garmin.com

今回僕が買った中華サイコン↑は、アプリを通してデータをFIT形式で出力できます。ただ、これはバイナリファイルなのでそのまま開いても人間には解読不能です。

そこで、FitCSVToolというありがたいプログラムが公式から配布されています。

developer.garmin.com

詳細は省きますが、javaを使える環境にダウンロードして

java -jar FitCSVTool.jar hogehoge.fit

hogehoge.csvが生成されるといった具合です。

使用デバイスによって何が書き出されるかは違うのですが、僕の場合次のようなCSVが生成されました

gist4b2b46ca9137e1d8b5492e01c8ce4795

なんか余計な項目が多くて見づらいですね…………

一応解説を入れます。上の方は無視して、11行目Data,8,record,...からを見てください

項目 解説 備考
timestamp タイムスタンプ。ただし、1989/12/31 00:00:00 UTCからの経過時間である*1 1017550364→UNIXtoDate(1017550364+631065600)=2022年03月30日13:52:44(JST)*2
position_lat,position_long 緯度,経度。ただし、単位がsemicircleとなっており要変換 degrees = semicircles * ( 180 / 231 )*3
distance,altitude 移動距離、高度[m] 高度はなんかずれが発生してるっぽいけどバイクで走ってるだけだからあまり気にしていない
speed 移動速度[m/s] 単位に注意

他は、要らんかなって……

緯度経度ですが、(426478976,1651643904)を例にとると、それぞれ180をかけて2の31乗で割ると(35.7470548153,138.439192772)となります。*4

スマホで使うために

ここからは、なんとなくしかわかってないので、何をしたら動くようになったかをメモしていく感じです。Androidアプリを作っていきます。そのへんの詳細は割愛。

jarをプロジェクトに組み込む

ダウンロードしたFIT SDKの中からFitCSVTool.jarをプロジェクトに追加します。

develop.hateblo.jp

こちらを参考に。以上。

アプリに外部ストレージの読み書き権限を与える

  • AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    package="com.example.fiteditapp">

    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
        tools:ignore="ScopedStorage" />
......

tools:ignore="ScopedStorage"は、AndroidStudioくんが入れろというので入れました。

パーミッションチェックも入れます。

...
public class MainActivity extends AppCompatActivity {
    private static final int PERMISSION_EX_STORAGE = 10884;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
...
        // permission check
        if(checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE)
                == PackageManager.PERMISSION_DENIED
            || checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)
                == PackageManager.PERMISSION_DENIED){
            System.out.println("Permission denied!");
            requestPermissions(new String[]{
                    Manifest.permission.READ_EXTERNAL_STORAGE,
                    Manifest.permission.WRITE_EXTERNAL_STORAGE
            },PERMISSION_EX_STORAGE);
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);

        // https://docs.kii.com/ja/guides/cloudsdk/android/quickstart/install-sdk/activity-implementation-note/
        if (requestCode == PERMISSION_EX_STORAGE)
            if (grantResults.length <= 0 || grantResults[0] != PackageManager.PERMISSION_GRANTED) {
                Toast.makeText(this, "権限がないため使えません", Toast.LENGTH_LONG).show();
            }
    }

適当にボタンを設置して、それを押したら端末からfitファイルを選択してFitCSVToolに突っ込むようにします。

...
    private static final int READ_FIT_FILE = 20884;
    private void openFile(){
        Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
        intent.addCategory(Intent.CATEGORY_OPENABLE);
        intent.setType("*/*"); //なんでも読み込み

        startActivityForResult(intent, READ_FIT_FILE);
    }
...
    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent resultData) {

        super.onActivityResult(requestCode, resultCode, resultData);

        if(resultCode != Activity.RESULT_OK){
            System.out.println("intent result error");
            return;
        }
        if(requestCode == READ_FIT_FILE){
            Uri uri = resultData.getData();
            if(canWriteFile()){
                String fullPath = getFullPath(uri);
                CSVTool.main(new String[]{fullPath});
            }
...
    }

    private String getFullPath(Uri uri){
        String path = uri.getPath().split(":")[1];
        String root = Environment.getExternalStorageDirectory().getAbsolutePath();
        return root + "/" + path;
    }

getFullPathというのは、ファイル選択したときに返ってくるuriから、FitCSVToolに投げられる形のファイルパスに変換する関数です。

getPath(uri)をすると/document/primary:fitcsv/hogehoge.fitみたいなのが帰ってきて、:の部分をEnvironment.getExternalStorageDirectory().getAbsolutePath()で得られる/storage/emulated/0に置き換える必要があります。*5

startActivityForResultは非推奨らしいのですが、僕はこれしか知らないのでこれで書きました。要はインテントスマホ内のファイルを選択してuriを取得できれば良いです。

実行

ファイルパスが取得できたらString []で送り込めばOKです。CSVToolで、アタマにFitがついてないので注意

諸々についてはこちらを見てください↓

github.com

参考

*1:https://developer.garmin.com/fit/protocol/ のFigure 4.

*2:1989/12/31 00:00:00 UTCUNIX時間が631065600なので

*3:-180~180度を符号付き32ビットに格納している

*4:道の駅にらさきのすぐ近くですね https://goo.gl/maps/nruijexnn6WbTMTu9

*5:結合するときに間に/を忘れずに入れる

プライバシーポリシー ・お問い合わせはこちら