Skip to content

Commit

Permalink
Add exporting of collected LocationDataSamples
Browse files Browse the repository at this point in the history
The collected LocationDataSamples are exported to a CSV file using the CSVFileExporter class.
  • Loading branch information
akahukas committed Feb 28, 2024
1 parent b9ec409 commit 7b96f34
Show file tree
Hide file tree
Showing 4 changed files with 234 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -81,5 +81,6 @@ dependencies {
// -------------------- ADDED BY SAKU HAKAMÄKI | START --------------------
implementation 'com.google.android.gms:play-services-maps:18.2.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'com.opencsv:opencsv:5.9'
// -------------------- ADDED BY SAKU HAKAMÄKI | END --------------------
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package com.google.ar.core.examples.java.geospatial;

import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
Expand Down Expand Up @@ -104,6 +105,7 @@

import fi.akahukas.projects.geospatial_maps.common.ObservableEnum;
import fi.akahukas.projects.geospatial_maps.common.ObservableInt;
import fi.akahukas.projects.geospatial_maps.location_data.CSVFileExporter;
import fi.akahukas.projects.geospatial_maps.location_data.CollectingMode;
import fi.akahukas.projects.geospatial_maps.location_data.LocationDataCollector;
import fi.akahukas.projects.geospatial_maps.location_data.LocationDataSample;
Expand Down Expand Up @@ -277,6 +279,9 @@ enum AnchorType {

private LocationDataCollector locationDataCollector;

private CSVFileExporter csvFileExporter;
private int csvFileExporterRequestCode;

private final int SINGLE_SAMPLE_COUNTDOWN_DURATION = 2000; // milliseconds
private final int SINGLE_SAMPLE_COUNTDOWN_INTERVAL = 1000; // milliseconds

Expand Down Expand Up @@ -358,6 +363,7 @@ public boolean onDown(MotionEvent e) {
});

locationDataCollector = new LocationDataCollector();
csvFileExporter = new CSVFileExporter(this);

recordButton = findViewById(R.id.record_button);
recordingStatusTextView = findViewById(R.id.recording_status_text_view);
Expand Down Expand Up @@ -1590,11 +1596,73 @@ protected boolean recordingMenuClick(MenuItem item) {
return true;
} else if (itemId == R.id.export_location_data) {

if (!locationDataCollector.getDataSampleSets().isEmpty()) {
this.csvFileExporterRequestCode = csvFileExporter.createFile();
} else {
Toast.makeText(
GeospatialActivity.this,
"Please save the collected sets before trying to export the data!",
Toast.LENGTH_LONG
).show();
}

return true;
}
return false;
}

/**
* Called when a launched Activity (e.g. system file picker) exits.
*
* @param requestCode The code the Activity was started with.
* @param resultCode The code the Activity returned.
* @param data The possible additional data that the Activity returned.
*/
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);

if (this.csvFileExporterRequestCode == requestCode) {
if (resultCode == Activity.RESULT_OK) {

if (data.getData() != null) {

// The file was successfully created, now try to write
// the collected data to it.
boolean isSuccess = csvFileExporter.writeToFile(
data.getData(),
locationDataCollector.getDataSampleSets());

if (isSuccess) {
Toast.makeText(
GeospatialActivity.this,
"File saved successfully.",
Toast.LENGTH_LONG
).show();

return;
}
}

Toast.makeText(
GeospatialActivity.this,
"An error occurred, please try again.",
Toast.LENGTH_LONG
).show();

} else if (resultCode == Activity.RESULT_CANCELED) {

Toast.makeText(
GeospatialActivity.this,
"ERROR: The operation was canceled. Please try again.",
Toast.LENGTH_LONG
).show();

return;
}
}
}

// -------------------- ADDED BY SAKU HAKAMÄKI | END --------------------

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
/*
---------- THIS FILE WAS CREATED BY SAKU HAKAMÄKI ----------
*/

package fi.akahukas.projects.geospatial_maps.location_data;


import static androidx.core.app.ActivityCompat.startActivityForResult;

import android.app.Activity;
import android.content.Intent;
import android.net.Uri;

import androidx.annotation.NonNull;

import com.opencsv.CSVWriter;

import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
import java.time.LocalTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;

/**
* Class responsible for creating and writing data held by
* collected LocationDataSamples to CSV files.
*/
public class CSVFileExporter {

private Activity startActivity_;

private final int CREATE_FILE_CODE = 1;

private final String DEFAULT_FILE_TYPE = "text/csv";
private final String DEFAULT_FILENAME = "geospatial_samples.csv";

/**
* Constructor method.
*
* @param startActivity The Activity where the system file
* picker is launched from.
*/
public CSVFileExporter(Activity startActivity) {
this.startActivity_ = startActivity;
}

/**
* Starts the Android system file picker to allow the user
* to select a location where to write the contents of the
* CSV file.
*
* @return The request code that is returned in
* onActivityResult() method of start Activity when the
* system file picker exits.
*/
public int createFile() {
Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType(DEFAULT_FILE_TYPE);
intent.putExtra(Intent.EXTRA_TITLE, DEFAULT_FILENAME);

startActivityForResult(this.startActivity_, intent, CREATE_FILE_CODE, null);

return CREATE_FILE_CODE;
}

/**
* Writes the collected LocationDataSamples to the CSV file
* created in {@link #createFile()} method.
*
* @param uri The URI of the created CSV file.
* @param sampleSets The collected LocationDataSamples.
* @return True if the write was successful, False otherwise.
*/
public boolean writeToFile(@NonNull Uri uri, @NonNull ArrayList<ArrayList<LocationDataSample>> sampleSets) {

try (OutputStream out = startActivity_.getContentResolver().openOutputStream(uri);
OutputStreamWriter writer = new OutputStreamWriter(out, StandardCharsets.UTF_8)) {

CSVWriter csvWriter = new CSVWriter(writer);

csvWriter.writeAll(
formatOutputData(sampleSets)
);

return true;

} catch (Exception e) {
e.printStackTrace();

return false;
}
}

/**
* Edits the collected LocationDataSamples to a format
* which is expected in the output CSV file.
*
* @param sampleSets The collected LocationDataSamples.
* @return The content to be written to the output CSV
* file in a List of String Arrays.
*/
private List<String[]> formatOutputData(ArrayList<ArrayList<LocationDataSample>> sampleSets) {
List<String[]> data = new ArrayList<String[]>();

// Header row.
data.add(new String[] {
"Set ID",
"Sample ID",
"Timestamp (hh:mm:ss:sss)",
"Latitude (degrees)",
"Longitude (degrees)",
"Horizontal Accuracy (metres)",
"Altitude (metres)",
"Vertical Accuracy (metres)",
"Quaternion",
"Yaw Accuracy (degrees)"
});

for (int i = 0; i < sampleSets.size(); i++) {
ArrayList<LocationDataSample> set = sampleSets.get(i);

for (int j = 0; j < set.size(); j++) {
LocationDataSample sample = set.get(j);

// One row for each LocationDataSample.
data.add(new String[] {
String.valueOf(i + 1), // Start from 1
String.valueOf(j + 1), // Start from 1
formatTimestamp(sample.getTimestamp()),
String.valueOf(sample.getLatitude()),
String.valueOf(sample.getLongitude()),
String.valueOf(sample.getHorizontalAccuracy()),
String.valueOf(sample.getAltitude()),
String.valueOf(sample.getVerticalAccuracy()),
Arrays.toString(sample.getQuaternion()),
String.valueOf(sample.getOrientationYawAccuracy())
});
}
}
return data;
}

/**
* Formats a LocalTime object to a format which is
* expected in the output CSV file.
*
* @param timestamp The LocalTime object to format.
* @return The formatted time as String in format
* hh:mm:ss:sss.
*/
private String formatTimestamp(LocalTime timestamp) {
return String.format(
Locale.ENGLISH,
"%02d:%02d:%02d:%d",
timestamp.getHour(),
timestamp.getMinute(),
timestamp.getSecond(),
(timestamp.getNano() / 1000000)
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -230,5 +230,4 @@ public ObservableInt getTotalSetsSize() {
public ObservableEnum<CollectingMode> getCurrentCollectingMode() {
return this.currentCollectingMode_;
}

}

0 comments on commit 7b96f34

Please sign in to comment.