GPX: export cumulative sensor data from SENSORPOINTs.

Part of #500.
This commit is contained in:
Dennis Guse
2021-12-04 21:35:23 +01:00
parent 89f9f25c55
commit 84c51ba8bb
2 changed files with 67 additions and 27 deletions
@@ -133,6 +133,7 @@ public class ExportImportTest {
// A sensor-only TrackPoint
mockBLESensorData(trackPointCreator, 15f, sensorDistance, 66f, 3f, 50f);
mockAltitudeChange(trackPointCreator, 1);
sendSensor(trackPointCreator, Instant.parse("2020-02-02T02:02:04Z"));
mockBLESensorData(trackPointCreator, 15f, sensorDistance, 66f, 3f, 50f);
@@ -255,10 +256,10 @@ public class ExportImportTest {
assertEquals(10, importedTrackStatistics.getMaxAltitude(), 0.01);
assertEquals(originalTrackStatistics.getTotalAltitudeGain(), importedTrackStatistics.getTotalAltitudeGain(), 0.01);
assertEquals(1, importedTrackStatistics.getTotalAltitudeGain(), 0.01);
assertEquals(2, importedTrackStatistics.getTotalAltitudeGain(), 0.01);
assertEquals(originalTrackStatistics.getTotalAltitudeLoss(), importedTrackStatistics.getTotalAltitudeLoss(), 0.01);
assertEquals(1, importedTrackStatistics.getTotalAltitudeLoss(), 0.01);
assertEquals(2, importedTrackStatistics.getTotalAltitudeLoss(), 0.01);
// 4. markers
assertMarkers();
@@ -326,6 +327,9 @@ public class ExportImportTest {
// Therefore, the track segmentation is changes.
List<TrackPoint> trackPointsWithCoordinates = trackPoints.stream().filter(it -> TrackPoint.Type.SEGMENT_START_AUTOMATIC.equals(it.getType()) || TrackPoint.Type.TRACKPOINT.equals(it.getType())).collect(Collectors.toList());
trackPointsWithCoordinates.get(0).setType(TrackPoint.Type.SEGMENT_START_AUTOMATIC);
trackPointsWithCoordinates.get(1).setSensorDistance(Distance.of(20));
trackPointsWithCoordinates.get(1).setAltitudeGain(1f);
trackPointsWithCoordinates.get(1).setAltitudeLoss(1f);
trackPointsWithCoordinates.get(2).setType(TrackPoint.Type.SEGMENT_START_AUTOMATIC);
TrackPointAssert a = new TrackPointAssert()
@@ -345,18 +349,18 @@ public class ExportImportTest {
assertEquals(Duration.ofSeconds(4), importedTrackStatistics.getMovingTime());
// Distance
assertEquals(Distance.of(50), importedTrackStatistics.getTotalDistance()); //TODO Should be 60 due to SensorPoint
assertEquals(Distance.of(60), importedTrackStatistics.getTotalDistance());
// Speed
assertEquals(Speed.of(15), importedTrackStatistics.getMaxSpeed());
assertEquals(Speed.of(12.5), importedTrackStatistics.getAverageSpeed());
assertEquals(Speed.of(12.5), importedTrackStatistics.getAverageMovingSpeed());
assertEquals(Speed.of(15), importedTrackStatistics.getAverageSpeed());
assertEquals(Speed.of(15), importedTrackStatistics.getAverageMovingSpeed());
// Altitude
assertEquals(10, importedTrackStatistics.getMinAltitude(), 0.01);
assertEquals(10, importedTrackStatistics.getMaxAltitude(), 0.01);
assertEquals(1, importedTrackStatistics.getTotalAltitudeGain(), 0.01);
assertEquals(1, importedTrackStatistics.getTotalAltitudeLoss(), 0.01);
assertEquals(2, importedTrackStatistics.getTotalAltitudeGain(), 0.01);
assertEquals(2, importedTrackStatistics.getTotalAltitudeLoss(), 0.01);
// 4. markers
assertMarkers();
@@ -437,6 +441,12 @@ public class ExportImportTest {
});
}
private void mockAltitudeChange(TrackPointCreator trackPointCreator, float altitudeGain) {
AltitudeSumManager altitudeSumManager = trackPointCreator.getAltitudeSumManager();
altitudeSumManager.setAltitudeGain_m(altitudeGain);
altitudeSumManager.setAltitudeLoss_m(altitudeGain);
}
private void sendSensor(TrackPointCreator trackPointCreator, Instant time) {
trackPointCreator.setClock(Clock.fixed(time, ZoneId.of("CET")));
trackPointCreator.onNewTrackPointWithoutGPS();
@@ -450,9 +460,7 @@ public class ExportImportTest {
location.setSpeed(speed);
location.setAltitude(altitude);
AltitudeSumManager altitudeSumManager = trackPointCreator.getAltitudeSumManager();
altitudeSumManager.setAltitudeGain_m(altitudeGain);
altitudeSumManager.setAltitudeLoss_m(altitudeGain);
mockAltitudeChange(trackPointCreator, altitudeGain);
trackPointCreator.setClock(Clock.fixed(time, ZoneId.of("CET")));
trackPointCreator.getGpsHandler().onLocationChanged(location);
@@ -24,7 +24,12 @@ import androidx.annotation.NonNull;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.text.NumberFormat;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.function.Function;
import java.util.stream.Stream;
import de.dennisguse.opentracks.content.data.Marker;
import de.dennisguse.opentracks.content.data.Track;
@@ -123,6 +128,8 @@ public class GPXTrackExporter implements TrackExporter {
boolean wroteTrack = false;
boolean wroteSegment = false;
LinkedList<TrackPoint> sensorPoints = new LinkedList<>();
try (TrackPointIterator trackPointIterator = contentProviderUtils.getTrackPointLocationIterator(track.getId(), null)) {
while (trackPointIterator.hasNext()) {
if (Thread.interrupted()) throw new InterruptedException();
@@ -150,10 +157,12 @@ public class GPXTrackExporter implements TrackExporter {
if (wroteSegment) writeCloseSegment();
writeOpenSegment();
wroteSegment = true;
writeTrackPoint(trackPoint);
writeTrackPoint(trackPoint, sensorPoints);
sensorPoints.clear();
break;
case SENSORPOINT:
//TODO We need somehow to compute the sensor data (mainly sensorDistance if present) and add it to the TRACKPOINT (if no segment followed in between)?
sensorPoints.add(trackPoint);
break;
case TRACKPOINT:
if (!wroteSegment) {
@@ -161,13 +170,21 @@ public class GPXTrackExporter implements TrackExporter {
writeOpenSegment();
wroteSegment = true;
}
writeTrackPoint(trackPoint);
writeTrackPoint(trackPoint, sensorPoints);
sensorPoints.clear();
break;
default:
throw new RuntimeException("Exporting this TrackPoint type is not implemented: " + trackPoint.getType());
}
}
if (!sensorPoints.isEmpty()) {
//TODO We might miss to export data; this happens if there are SENSORPOINTs after the final TRACKPOINT of a track.
//For segments the data is added to the next segment.
Log.d(TAG, "SENSORPOINTs after final TRACKPOINT; this data is not exported.");
}
if (wroteSegment) {
// Should not be necessary as tracks should end with SEGMENT_END_MANUAL.
// Anyhow, make sure that the last segment is closed.
@@ -293,7 +310,7 @@ public class GPXTrackExporter implements TrackExporter {
printWriter.println("</trkseg>");
}
public void writeTrackPoint(TrackPoint trackPoint) {
public void writeTrackPoint(TrackPoint trackPoint, List<TrackPoint> sensorPoints) {
if (printWriter != null) {
printWriter.println("<trkpt " + formatLocation(trackPoint.getLatitude(), trackPoint.getLongitude()) + ">");
@@ -304,44 +321,59 @@ public class GPXTrackExporter implements TrackExporter {
printWriter.println("<time>" + StringUtils.formatDateTimeIso8601(trackPoint.getTime()) + "</time>");
if (trackPoint.hasSpeed() || trackPoint.hasHeartRate() || trackPoint.hasCadence() || trackPoint.hasAltitudeGain() || trackPoint.hasAltitudeLoss()) {
printWriter.println("<extensions><gpxtpx:TrackPointExtension>");
{
String trackPointExtensionContent = "";
if (trackPoint.hasSpeed()) {
printWriter.println("<gpxtpx:speed>" + SPEED_FORMAT.format(trackPoint.getSpeed().toMPS()) + "</gpxtpx:speed>");
trackPointExtensionContent += "<gpxtpx:speed>" + SPEED_FORMAT.format(trackPoint.getSpeed().toMPS()) + "</gpxtpx:speed>\n";
}
if (trackPoint.hasHeartRate()) {
printWriter.println("<gpxtpx:hr>" + HEARTRATE_FORMAT.format(trackPoint.getHeartRate_bpm()) + "</gpxtpx:hr>");
trackPointExtensionContent += "<gpxtpx:hr>" + HEARTRATE_FORMAT.format(trackPoint.getHeartRate_bpm()) + "</gpxtpx:hr>\n";
}
if (trackPoint.hasCadence()) {
printWriter.println("<gpxtpx:cad>" + CADENCE_FORMAT.format(trackPoint.getCadence_rpm()) + "</gpxtpx:cad>");
trackPointExtensionContent += "<gpxtpx:cad>" + CADENCE_FORMAT.format(trackPoint.getCadence_rpm()) + "</gpxtpx:cad>\n";
}
if (trackPoint.hasPower()) {
printWriter.println("<pwr:PowerInWatts>" + POWER_FORMAT.format(trackPoint.getPower()) + "</pwr:PowerInWatts>");
trackPointExtensionContent += "<pwr:PowerInWatts>" + POWER_FORMAT.format(trackPoint.getPower()) + "</pwr:PowerInWatts>\n";
}
if (trackPoint.hasAltitudeGain()) {
printWriter.println("<opentracks:gain>" + ALTITUDE_FORMAT.format(trackPoint.getAltitudeGain()) + "</opentracks:gain>");
Double cumulativeGain = cumulateSensorData(trackPoint, sensorPoints, (tp) -> tp.hasAltitudeGain() ? (double) tp.getAltitudeGain() : null);
if (cumulativeGain != null) {
trackPointExtensionContent += ("<opentracks:gain>" + ALTITUDE_FORMAT.format(cumulativeGain) + "</opentracks:gain>\n");
}
if (trackPoint.hasAltitudeLoss()) {
printWriter.println("<opentracks:loss>" + ALTITUDE_FORMAT.format(trackPoint.getAltitudeLoss()) + "</opentracks:loss>");
Double cumulativeLoss = cumulateSensorData(trackPoint, sensorPoints, (tp) -> tp.hasAltitudeLoss() ? (double) tp.getAltitudeLoss() : null);
if (cumulativeLoss != null) {
trackPointExtensionContent += ("<opentracks:loss>" + ALTITUDE_FORMAT.format(cumulativeLoss) + "</opentracks:loss>\n");
}
if (trackPoint.hasSensorDistance()) {
printWriter.println("<opentracks:distance>" + DISTANCE_FORMAT.format(trackPoint.getSensorDistance().toM()) + "</opentracks:distance>");
Double cumulativeDistance = cumulateSensorData(trackPoint, sensorPoints, (tp) -> tp.hasSensorDistance() ? tp.getSensorDistance().toM() : null);
if (cumulativeDistance != null) {
trackPointExtensionContent += ("<opentracks:distance>" + ALTITUDE_FORMAT.format(cumulativeDistance) + "</opentracks:distance>\n");
}
printWriter.println("</gpxtpx:TrackPointExtension></extensions>");
if (!trackPointExtensionContent.isEmpty()) {
printWriter.println("<extensions><gpxtpx:TrackPointExtension>");
printWriter.print(trackPointExtensionContent);
printWriter.println("</gpxtpx:TrackPointExtension></extensions>");
}
}
printWriter.println("</trkpt>");
}
}
private Double cumulateSensorData(TrackPoint trackPoint, List<TrackPoint> sensorPoints, Function<TrackPoint, Double> map) {
return Stream.concat(sensorPoints.stream(), Stream.of(trackPoint))
.map(map)
.filter(Objects::nonNull)
.reduce((gain, next) -> gain + next)
.orElse(null);
}
private String formatLocation(double latitude, double longitude) {
return "lat=\"" + COORDINATE_FORMAT.format(latitude) + "\" lon=\"" + COORDINATE_FORMAT.format(longitude) + "\"";
}