From 84c51ba8bb29686bc500d2732e92793dec310840 Mon Sep 17 00:00:00 2001 From: Dennis Guse Date: Sat, 4 Dec 2021 21:35:23 +0100 Subject: [PATCH] GPX: export cumulative sensor data from SENSORPOINTs. Part of #500. --- .../io/file/importer/ExportImportTest.java | 28 +++++--- .../io/file/exporter/GPXTrackExporter.java | 66 ++++++++++++++----- 2 files changed, 67 insertions(+), 27 deletions(-) diff --git a/src/androidTest/java/de/dennisguse/opentracks/io/file/importer/ExportImportTest.java b/src/androidTest/java/de/dennisguse/opentracks/io/file/importer/ExportImportTest.java index 53d8fcd2b..da1adcaf3 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/io/file/importer/ExportImportTest.java +++ b/src/androidTest/java/de/dennisguse/opentracks/io/file/importer/ExportImportTest.java @@ -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 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); diff --git a/src/main/java/de/dennisguse/opentracks/io/file/exporter/GPXTrackExporter.java b/src/main/java/de/dennisguse/opentracks/io/file/exporter/GPXTrackExporter.java index 9305f19f5..7a97d170b 100644 --- a/src/main/java/de/dennisguse/opentracks/io/file/exporter/GPXTrackExporter.java +++ b/src/main/java/de/dennisguse/opentracks/io/file/exporter/GPXTrackExporter.java @@ -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 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(""); } - public void writeTrackPoint(TrackPoint trackPoint) { + public void writeTrackPoint(TrackPoint trackPoint, List sensorPoints) { if (printWriter != null) { printWriter.println(""); @@ -304,44 +321,59 @@ public class GPXTrackExporter implements TrackExporter { printWriter.println(""); - if (trackPoint.hasSpeed() || trackPoint.hasHeartRate() || trackPoint.hasCadence() || trackPoint.hasAltitudeGain() || trackPoint.hasAltitudeLoss()) { - printWriter.println(""); + { + String trackPointExtensionContent = ""; if (trackPoint.hasSpeed()) { - printWriter.println("" + SPEED_FORMAT.format(trackPoint.getSpeed().toMPS()) + ""); + trackPointExtensionContent += "" + SPEED_FORMAT.format(trackPoint.getSpeed().toMPS()) + "\n"; } if (trackPoint.hasHeartRate()) { - printWriter.println("" + HEARTRATE_FORMAT.format(trackPoint.getHeartRate_bpm()) + ""); + trackPointExtensionContent += "" + HEARTRATE_FORMAT.format(trackPoint.getHeartRate_bpm()) + "\n"; } if (trackPoint.hasCadence()) { - printWriter.println("" + CADENCE_FORMAT.format(trackPoint.getCadence_rpm()) + ""); + trackPointExtensionContent += "" + CADENCE_FORMAT.format(trackPoint.getCadence_rpm()) + "\n"; } if (trackPoint.hasPower()) { - printWriter.println("" + POWER_FORMAT.format(trackPoint.getPower()) + ""); + trackPointExtensionContent += "" + POWER_FORMAT.format(trackPoint.getPower()) + "\n"; } - if (trackPoint.hasAltitudeGain()) { - printWriter.println("" + ALTITUDE_FORMAT.format(trackPoint.getAltitudeGain()) + ""); + Double cumulativeGain = cumulateSensorData(trackPoint, sensorPoints, (tp) -> tp.hasAltitudeGain() ? (double) tp.getAltitudeGain() : null); + if (cumulativeGain != null) { + trackPointExtensionContent += ("" + ALTITUDE_FORMAT.format(cumulativeGain) + "\n"); } - if (trackPoint.hasAltitudeLoss()) { - printWriter.println("" + ALTITUDE_FORMAT.format(trackPoint.getAltitudeLoss()) + ""); + Double cumulativeLoss = cumulateSensorData(trackPoint, sensorPoints, (tp) -> tp.hasAltitudeLoss() ? (double) tp.getAltitudeLoss() : null); + if (cumulativeLoss != null) { + trackPointExtensionContent += ("" + ALTITUDE_FORMAT.format(cumulativeLoss) + "\n"); } - if (trackPoint.hasSensorDistance()) { - printWriter.println("" + DISTANCE_FORMAT.format(trackPoint.getSensorDistance().toM()) + ""); + Double cumulativeDistance = cumulateSensorData(trackPoint, sensorPoints, (tp) -> tp.hasSensorDistance() ? tp.getSensorDistance().toM() : null); + if (cumulativeDistance != null) { + trackPointExtensionContent += ("" + ALTITUDE_FORMAT.format(cumulativeDistance) + "\n"); } - printWriter.println(""); + if (!trackPointExtensionContent.isEmpty()) { + printWriter.println(""); + printWriter.print(trackPointExtensionContent); + printWriter.println(""); + } } printWriter.println(""); } } + private Double cumulateSensorData(TrackPoint trackPoint, List sensorPoints, Function 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) + "\""; }