トップ地図ユーティリティ > GPSLog変換

GPSLog変換

はじめに

自作地図アプリは8年前にWindows C#で開発したものをAndroidスマホに移植したものであり、 GPSログは8年間の蓄積があり、途中で何回かファイル形式を少し変更している。

過去のGPSログ表示プログラムの簡単化とパフォーマンス向上のために、全てのログを最新フォーマットに変換しておく。

元のGPSログファイル名には日付情報のみを含むが、過去ログ表示用ログファイルの名前には、日付の他に境界ボックス座標値も含める。

地図アプリ起動後、初めて、過去ログを表示するときに、ファイル名一覧を読み込む。

MapUtilアプリ

ごくまれにしか実行しない機能は地図アプリから切り離し、ユーティリテイアプリとする。一般には、時間がかかる処理が多いため、 バッググラウンドで動くサービスとする。

Androidアプリ開発の学習のために、一から作成する。

開発手順

プロジェクト作成では Empty Activity を選択する。 最初の AndroidManifest.xml は ここにある通りである。

Androidエミュレータ(Android 9)で実行すると、6つのエラーが出た。最初のエラーは以下の通り。

  1.  Dependency 'androidx.appcompat:appcompat-resources:1.6.1' requires libraries and applications that
      depend on it to compile against version 33 or later of the
      Android APIs.

      :app is currently compiled against android-32.

      Recommended action: Update this project to use a newer compileSdkVersion
      of at least 33, for example 33.

MapXとMapUtilの build.gradle を比較してみる。 最初のエラーは'androidx.appcompat:appcompat-resources:1.6.1'は Android 10以上用であり、 Android 9で実行するには 1.5.1 に下げる必要があるということである。

しかし、ここだけ変更すると
Not targeting the latest versions of Android; compatibility modes apply. Consider testing and updating this version. Consult the android.os.Build.VERSION_CODES javadoc for details.
という結果になった。この修正はやめ、compileSDK、targetSDK を 33 に変えてみた。これで動いた。
[MapXのbuild.gradle]

plugins {
    id 'com.android.application'
}

android {
    namespace 'com.example.mapx'
    compileSdk 32

    defaultConfig {
        applicationId "com.example.mapx"
        minSdk 28
        targetSdk 32
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
}

dependencies {
    implementation 'androidx.appcompat:appcompat:1.5.1'
    implementation 'com.google.android.material:material:1.7.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
    implementation 'com.google.android.gms:play-services-location:21.0.1'
    testImplementation 'junit:junit:4.13.2'
    androidTestImplementation 'androidx.test.ext:junit:1.1.4'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.0'
}
[MapUtilのbuild.gradle]
plugins {
    id 'com.android.application'
}

android {
    namespace 'com.example.maputil'
    compileSdk 32

    defaultConfig {
        applicationId "com.example.maputil"
        minSdk 28
        targetSdk 32
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
}

dependencies {
    implementation 'androidx.appcompat:appcompat:1.6.1'
    implementation 'com.google.android.material:material:1.9.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
    testImplementation 'junit:junit:4.13.2'
    androidTestImplementation 'androidx.test.ext:junit:1.1.5'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
}

ツールバー・メニューを追加する

res/menu/my_toolbar.xml を追加する。

onCreateOptionsMenu() を追加する。

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        MenuInflater inflater = getMenuInflater();
        inflater.inflate(R.menu.my_toolbar, menu);
        MenuCompat.setGroupDividerEnabled(menu, true);
        return true;
    }
}

class GPSLog

現在の地図アプリにおける class GPSLog は以下の通りである。

public class GPSLog {
    final static int E7 = 10000000;
    static List listLogs;    // 今日のGPSログ

    LocalDateTime ldt;
    int ilon, ilat, ialt;
    float speed, accuracy = -1;

    public GPSLog(String loc) {
        String[] v = loc.split(",");
        ilon = (int)Math.round(Double.parseDouble(v[1]) * E7);
        ilat = (int)Math.round(Double.parseDouble(v[2]) * E7);
        ialt = (int)Math.round(Double.parseDouble(v[3]));
        speed = Float.parseFloat(v[4]);
        ldt = LocalDateTime.now();      // 2022.5.11
    }

    // 時:分:秒, lon, lat, alt, speed, accuracy,
    //19:52:17,139.5044180,35.5452761,96.4,0.0,9.9
    public GPSLog(int date, String line) {
        String[] v = line.split("[,:]");
        int year = date / 10000;
        int month = (date % 10000) / 100;
        int day = date % 100;
        ldt = LocalDateTime.of(year, month, day,        // 2021/1/1 10:20:30.000
                Integer.parseInt(v[0]), Integer.parseInt(v[1]), Integer.parseInt(v[2]));
        ilon = (int)Math.round(Double.parseDouble(v[3]) * E7);
        ilat = (int)Math.round(Double.parseDouble(v[4]) * E7);
        ialt = (int)Math.round(Double.parseDouble(v[5]));
        if (v.length >= 8) {
            speed    = Float.parseFloat(v[6]);
            accuracy = Float.parseFloat(v[7]);
        }
    }

    public static void LoadGPSLog() {
        LocalDateTime now = LocalDateTime.now();
        int date = now.getYear()*10000 + now.getMonthValue()*100 + now.getDayOfMonth();
        listLogs = LoadGPSLog(date);
    }

    // ファイル読み込み
    public static List LoadGPSLog(int date) {
        List listLogs = new ArrayList<>();
        String file = String.format(Locale.JAPAN, "%sgpslogs/%d.csv", Map.DIR, date);
        int lastLon = 0, lastLat = 0;
        //Log.d("GPSLog", file);
        if (new File(file).exists()) {
            try {
                Path path = Paths.get(file);
                List lines = Files.readAllLines(path);
                for (String line : lines) {
                    GPSLog log = new GPSLog(date, line);
                    if (log.ilon != lastLon || log.ilat != lastLat) {
                        lastLon = log.ilon;
                        lastLat = log.ilat;
                        listLogs.add(log);
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return listLogs;
    }
}

初期のデータ(2015.3.14~)は以下の通りである。

448098102,139.504442,35.545372,94.106010

先頭の 448098102 は 2001年1月1日からの通算秒である。Speed および Accuracy はない。

すぐに(2015.4.1~2021.11.30)、時刻のフォーマットを現在の形に変更した。ファイル名は p20150401.csv の形である。

17:45:48,139.504262,35.545331,91.9

したがって、最初の半月分は無視してもよいだろう。

2022.1.27 からスマホの地図アプリで現在のフォーマットとした。2021.12.1 から 2022.1.26 の2か月弱はGPSログはとっていない。 ファイル名は 20220127.csv の形とした。


Speed は徒歩か車かの区別に利用している。単位は「m/秒」である。Windows時代にはこの値を取得、保存していなかった。 この値がある方が表示プログラムの負担が減るので、補うことにする。

Windowsのときは極座標で直接、徒歩か車かを判定していたが、今回は、次のプログラムで、2点間の極座標から2点間の距離(単位:m)を求めて、 その値を2点間の時刻差(単位:秒)で割って、速度(m/秒単位)とする。

    double deg2rad(double deg) { return (deg/180.0)*Math.PI; }

    double calculateDistance(double lonA, double latA, double lonB, double latB) {
        double latAvg = deg2rad(latA + ((latB - latA) / 2));
        double latDifference = deg2rad(latA - latB);
        double lonDifference = deg2rad(lonA - lonB);
        double curRadiusTemp = 1 - 0.00669438 * Math.pow(Math.sin(latAvg), 2);
        double meridianCurvatureRadius = 6335439.327/Math.sqrt(Math.pow(curRadiusTemp,3));
        double primeVerticalCircleCurvatureRadius = 6378137 / Math.sqrt(curRadiusTemp);
        double distance2 = Math.pow(meridianCurvatureRadius*latDifference,2) +
                Math.pow(primeVerticalCircleCurvatureRadius*Math.cos(latAvg)*lonDifference,2);
        return Math.sqrt(distance2);
    }

リファレンス

[1]