diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 0a128af..61c00c1 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -26,8 +26,8 @@ android { } } compileOptions { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 } } @@ -50,4 +50,9 @@ dependencies { // optional - Guava support for Room, including Optional and ListenableFuture implementation("androidx.room:room-guava:$room_version") + + implementation("io.jenetics:jpx:3.1.0") + // https://mvnrepository.com/artifact/org.codehaus.woodstox/woodstox-core-asl + implementation("org.codehaus.woodstox:woodstox-core-asl:4.4.1") + } \ No newline at end of file diff --git a/app/src/main/java/com/proculite/logmylocation/MainActivity.java b/app/src/main/java/com/proculite/logmylocation/MainActivity.java index a47be33..dfcb3f5 100644 --- a/app/src/main/java/com/proculite/logmylocation/MainActivity.java +++ b/app/src/main/java/com/proculite/logmylocation/MainActivity.java @@ -9,6 +9,7 @@ import android.location.LocationListener; import android.os.Bundle; import android.os.IBinder; import android.util.Log; +import android.widget.Button; import android.widget.TextView; import androidx.activity.EdgeToEdge; @@ -18,13 +19,27 @@ import androidx.core.graphics.Insets; import androidx.core.view.ViewCompat; import androidx.core.view.WindowInsetsCompat; +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.PipedInputStream; +import java.io.PipedOutputStream; +import java.io.Writer; + +import io.jenetics.jpx.GPX; +import io.jenetics.jpx.WayPoint; + public class MainActivity extends AppCompatActivity implements LocationListener, ServiceConnection { private final String TAG = MainActivity.class.getName(); + private Button exportButton; + private WriteToFile writeToFile; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + writeToFile = new WriteToFile(this,"application/gpx"); + Intent serviceIntent = new Intent(this, LocationLoggingService.class); startForegroundService(serviceIntent); @@ -35,6 +50,8 @@ public class MainActivity extends AppCompatActivity implements LocationListener, v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom); return insets; }); + + exportButton = findViewById(R.id.buttonExport); } @Override @@ -69,6 +86,73 @@ public class MainActivity extends AppCompatActivity implements LocationListener, LocationLoggingServiceBinder binder = (LocationLoggingServiceBinder) iBinder; binder.subscribeToLocationUpdates(this); + + if(exportButton != null){ + exportButton.setOnClickListener(v -> { + Log.d(TAG, "Export button clicked."); + new Thread(() -> { + GPX gpx = GPX.builder().addTrack(track -> track.addSegment(segment -> { + for(LocationEntity location : binder.allLocations()){ + segment.addPoint(locationToWayPoint(location)); + } + })).build(); + Log.d(TAG, "Built GPX."); + + writeToFile.write("output.gpx", outputStream -> { + try { + GPX.Writer.of(GPX.Writer.Indent.SPACE4).write(gpx, outputStream); + } catch (IOException e) { + Log.e(TAG, "Failed to write."); + } + }); + }).start(); + }); + } + } + + private static WayPoint locationToWayPoint(LocationEntity location) { + WayPoint.Builder builder = WayPoint.builder() + .lat(location.latitude) + .lon(location.longitude) + .time(location.unixTime); + + if(location.speed != null){ + builder.speed(location.speed); + } + + if(location.bearing != null){ + builder.course(location.bearing); + } + + if(location.altitude != null){ + builder.ele(location.altitude); + } + + StringBuilder commentBuilder = new StringBuilder(); + + if(location.isMock){ + commentBuilder.append("Location is mock.\n"); + } + + if(location.accuracy != null){ + commentBuilder.append(String.format("Accuracy is expected to be within %s meters.\n", location.accuracy)); + } + + if(location.altitudeAccuracy != null){ + commentBuilder.append(String.format("Altitude accuracy is expected to be within %s meters.\n", location.altitudeAccuracy)); + } + + if(location.bearingAccuracy != null){ + commentBuilder.append(String.format("Bearing accuracy is expected to be within %s degrees.\n", location.bearingAccuracy)); + } + + if(location.speedAccuracy != null){ + commentBuilder.append(String.format("Speed accuracy is expected to be within %s meters per second.\n", location.speedAccuracy)); + } + + builder.cmt(commentBuilder.toString()); + + return builder.build(); } @Override diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 3968ff2..01447c5 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -18,4 +18,13 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> +