自作地図アプリは8年前にWindows C#で開発したものをAndroidスマホに移植したものであり、 GPSログは8年間の蓄積があり、途中で何回かファイル形式を少し変更している。
過去のGPSログ表示プログラムの簡単化とパフォーマンス向上のために、全てのログを最新フォーマットに変換しておく。
元のGPSログファイル名には日付情報のみを含むが、過去ログ表示用ログファイルの名前には、日付の他に境界ボックス座標値も含める。
地図アプリ起動後、初めて、過去ログを表示するときに、ファイル名一覧を読み込む。
ごくまれにしか実行しない機能は地図アプリから切り離し、ユーティリテイアプリとする。一般には、時間がかかる処理が多いため、 バッググラウンドで動くサービスとする。
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 は以下の通りである。
public class GPSLog { final static int E7 = 10000000; static ListlistLogs; // 今日の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); }