diff --git a/doc/opentracks-schema-1.0.xsd b/doc/opentracks-schema-1.0.xsd index 55c2b3d4b..c9eba6e4f 100644 --- a/doc/opentracks-schema-1.0.xsd +++ b/doc/opentracks-schema-1.0.xsd @@ -1,8 +1,32 @@ - - + + + + A unique track identifier stored as UUID. + + + - \ No newline at end of file + + + + The lost elevation in meters from the previous to the current TrackPoint. + Only used in GPX. + + + + + + + The gained elevation in meters from the previous to the current TrackPoint. + Only used in GPX. + + + + \ No newline at end of file diff --git a/src/androidTest/java/de/dennisguse/opentracks/content/sensor/SensorDataCyclingTest.java b/src/androidTest/java/de/dennisguse/opentracks/content/sensor/SensorDataCyclingTest.java index 8f8dd01d9..491133f83 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/content/sensor/SensorDataCyclingTest.java +++ b/src/androidTest/java/de/dennisguse/opentracks/content/sensor/SensorDataCyclingTest.java @@ -96,34 +96,36 @@ public class SensorDataCyclingTest { @Test public void compute_speed() { // given - SensorDataCycling.Speed previous = new SensorDataCycling.Speed("sensorAddress", "sensorName", 1, 6184); - SensorDataCycling.Speed current = new SensorDataCycling.Speed("sensorAddress", "sensorName", 2, 8016); + SensorDataCycling.DistanceSpeed previous = new SensorDataCycling.DistanceSpeed("sensorAddress", "sensorName", 1, 6184); + SensorDataCycling.DistanceSpeed current = new SensorDataCycling.DistanceSpeed("sensorAddress", "sensorName", 2, 8016); // when current.compute(previous, 2150); // then - assertEquals(1.20, current.getValue(), 0.01); + assertEquals(1.20, current.getValue().distance_m, 2150); + assertEquals(1.20, current.getValue().speed_mps, 0.01); } @Test public void compute_speed_rollOverCount() { // given - SensorDataCycling.Speed previous = new SensorDataCycling.Speed("sensorAddress", "sensorName", UintUtils.UINT16_MAX - 1, 1024); - SensorDataCycling.Speed current = new SensorDataCycling.Speed("sensorAddress", "sensorName", 0, 2048); + SensorDataCycling.DistanceSpeed previous = new SensorDataCycling.DistanceSpeed("sensorAddress", "sensorName", UintUtils.UINT16_MAX - 1, 1024); + SensorDataCycling.DistanceSpeed current = new SensorDataCycling.DistanceSpeed("sensorAddress", "sensorName", 0, 2048); // when current.compute(previous, 2000); // then - assertEquals(2, current.getValue(), 0.01); + assertEquals(1.20, current.getValue().distance_m, 2000); + assertEquals(2, current.getValue().speed_mps, 0.01); } @Test public void equals_speed_with_no_data() { // given - SensorDataCycling.Speed previous = new SensorDataCycling.Speed("sensorAddress"); - SensorDataCycling.Speed current = new SensorDataCycling.Speed("sensorAddress", "sensorName", 0, 2048); + SensorDataCycling.DistanceSpeed previous = new SensorDataCycling.DistanceSpeed("sensorAddress"); + SensorDataCycling.DistanceSpeed current = new SensorDataCycling.DistanceSpeed("sensorAddress", "sensorName", 0, 2048); // when previous.toString(); 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 8d4e064be..1b1db95e3 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 @@ -3,7 +3,6 @@ package de.dennisguse.opentracks.io.file.importer; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; -import android.location.Location; import android.os.Looper; import android.util.Log; @@ -24,6 +23,7 @@ import org.junit.runners.JUnit4; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.InputStream; +import java.time.Instant; import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; @@ -91,17 +91,17 @@ public class ExportImportTest { trackId = service.startNewTrack(); - service.newTrackPoint(createTrackPoint(System.currentTimeMillis(), 3, 14, 10, 15, 10, 0, 66, 3, 50), 0); + service.newTrackPoint(createTrackPoint(System.currentTimeMillis(), 3, 14, 10, 15, 10, 0, 66, 3, 50, 5), 0); service.insertMarker("Marker 1", "Marker 1 category", "Marker 1 desc", null); - service.newTrackPoint(createTrackPoint(System.currentTimeMillis(), 3, 14.001, 10, 15, 10, 0, 66, 3, 50), 0); - service.newTrackPoint(createTrackPoint(System.currentTimeMillis(), 3, 14.002, 10, 15, 10, 0, 66, 3, 50), 0); + service.newTrackPoint(createTrackPoint(System.currentTimeMillis(), 3, 14.001, 10, 15, 10, 0, 66, 3, 50, 5), 0); + service.newTrackPoint(createTrackPoint(System.currentTimeMillis(), 3, 14.002, 10, 15, 10, 0, 66, 3, 50, 5), 0); service.insertMarker("Marker 2", "Marker 2 category", "Marker 2 desc", null); service.pauseCurrentTrack(); service.resumeCurrentTrack(); - service.newTrackPoint(createTrackPoint(System.currentTimeMillis(), 3, 14.003, 10, 15, 10, 0, 66, 3, 50), 0); - service.newTrackPoint(createTrackPoint(System.currentTimeMillis(), 3, 16, 10, 15, 10, 0, 66, 3, 50), 0); - service.newTrackPoint(createTrackPoint(System.currentTimeMillis(), 3, 16.001, 10, 15, 10, 0, 66, 3, 50), 0); + service.newTrackPoint(createTrackPoint(System.currentTimeMillis(), 3, 14.003, 10, 15, 10, 0, 66, 3, 50, 5), 0); + service.newTrackPoint(createTrackPoint(System.currentTimeMillis(), 3, 16, 10, 15, 10, 0, 66, 3, 50, 5), 0); + service.newTrackPoint(createTrackPoint(System.currentTimeMillis(), 3, 16.001, 10, 15, 10, 0, 66, 3, 50, 5), 0); service.endCurrentTrack(); track = contentProviderUtils.getTrack(trackId); @@ -163,10 +163,10 @@ public class ExportImportTest { assertEquals(track.getUuid(), importedTrack.getUuid()); // 2. trackpoints - assertTrackpoints(trackPoints, false, false, false, false, false); + assertTrackpoints(trackPoints, false, false, false, false, false, false); // 3. trackstatistics - assertTrackStatistics(false, false); + assertTrackStatistics(false, false, false); // 4. markers assertMarkers(); @@ -201,10 +201,10 @@ public class ExportImportTest { assertEquals(track.getIcon(), importedTrack.getIcon()); // 2. trackpoints - assertTrackpoints(trackPoints, true, true, true, true, true); + assertTrackpoints(trackPoints, true, true, true, true, true, true); // 2. trackstatistics - assertTrackStatistics(false, true); + assertTrackStatistics(false, true, true); // 4. markers assertMarkers(); @@ -234,38 +234,6 @@ public class ExportImportTest { assertNull(importedTrack); } - @Ignore - @LargeTest - @Test - public void kmz_only_track() { - // TODO - Log.e(TAG, "Test not implemented."); - } - - @Ignore - @LargeTest - @Test - public void kmz_with_trackdetail() { - // TODO - Log.e(TAG, "Test not implemented."); - } - - @Ignore - @LargeTest - @Test - public void kmz_with_trackdetail_and_sensordata() { - // TODO - Log.e(TAG, "Test not implemented."); - } - - @Ignore - @LargeTest - @Test - public void kmz_with_trackdetail_and_sensordata_and_pictures() { - // TODO - Log.e(TAG, "Test not implemented."); - } - @LargeTest @Test public void gpx() { @@ -303,10 +271,10 @@ public class ExportImportTest { trackPointsWithCoordinates.get(0).setType(TrackPoint.Type.SEGMENT_START_AUTOMATIC); trackPointsWithCoordinates.get(3).setType(TrackPoint.Type.SEGMENT_START_AUTOMATIC); - assertTrackpoints(trackPointsWithCoordinates, true, true, true, true, true); + assertTrackpoints(trackPointsWithCoordinates, true, true, true, true, true, false); // 3. trackstatistics - assertTrackStatistics(true, true); + assertTrackStatistics(true, true, false); // 4. markers assertMarkers(); @@ -356,7 +324,7 @@ public class ExportImportTest { } } - private void assertTrackpoints(List trackPoints, boolean verifyPower, boolean verifyHeartrate, boolean verifyCadence, boolean verifyElevationGain, boolean verifyElevationLoss) { + private void assertTrackpoints(List trackPoints, boolean verifyPower, boolean verifyHeartrate, boolean verifyCadence, boolean verifyElevationGain, boolean verifyElevationLoss, boolean verifyDistance) { List importedTrackPoints = contentProviderUtils.getTrackPoints(importTrackId); assertEquals(trackPoints.size(), importedTrackPoints.size()); @@ -407,10 +375,13 @@ public class ExportImportTest { if (verifyElevationLoss) { assertEquals(trackPoint.getElevationLoss(), importedTrackPoint.getElevationLoss(), 0.01); } + if (verifyDistance) { + assertEquals(trackPoint.getSensorDistance(), importedTrackPoint.getSensorDistance(), 0.01); + } } } - private void assertTrackStatistics(boolean isGpx, boolean verifyElevationGainAndLoss) { + private void assertTrackStatistics(boolean isGpx, boolean verifyElevationGainAndLoss, boolean verifyDistance) { Track importedTrack = contentProviderUtils.getTrack(importTrackId); assertNotNull(importedTrack.getTrackStatistics()); @@ -428,7 +399,9 @@ public class ExportImportTest { assertEquals(trackStatistics.getMovingTime(), importedTrackStatistics.getMovingTime()); // Distance - assertEquals(trackStatistics.getTotalDistance(), importedTrackStatistics.getTotalDistance(), 0.01); + if (verifyDistance) { + assertEquals(trackStatistics.getTotalDistance(), importedTrackStatistics.getTotalDistance(), 0.01); + } // Speed assertEquals(trackStatistics.getMaxSpeed(), importedTrackStatistics.getMaxSpeed(), 0.01); @@ -448,20 +421,15 @@ public class ExportImportTest { } } - private static TrackPoint createTrackPoint(long time, double latitude, double longitude, float accuracy, long speed, long altitude, float elevationGain, float heartRate, float cyclingCadence, float power) { - Location location = new Location(""); - location.setTime(time); - location.setLongitude(longitude); - location.setLatitude(latitude); - location.setAccuracy(accuracy); - location.setAltitude(altitude); - location.setSpeed(speed); - - TrackPoint tp = new TrackPoint(location); + private static TrackPoint createTrackPoint(long time, double latitude, double longitude, float accuracy, float speed, float altitude, float elevationGain, float heartRate, float cyclingCadence, float power, float distance) { + TrackPoint tp = new TrackPoint(latitude, longitude, (double) altitude, Instant.ofEpochMilli(time)); + tp.setAccuracy(accuracy); + tp.setSpeed(speed); tp.setHeartRate_bpm(heartRate); tp.setCyclingCadence_rpm(cyclingCadence); tp.setPower(power); tp.setElevationGain(elevationGain); + tp.setSensorDistance(distance); return tp; } } \ No newline at end of file diff --git a/src/androidTest/java/de/dennisguse/opentracks/stats/TrackStatisticsUpdaterTest.java b/src/androidTest/java/de/dennisguse/opentracks/stats/TrackStatisticsUpdaterTest.java index 90a25602a..0b439d9c0 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/stats/TrackStatisticsUpdaterTest.java +++ b/src/androidTest/java/de/dennisguse/opentracks/stats/TrackStatisticsUpdaterTest.java @@ -1,27 +1,36 @@ package de.dennisguse.opentracks.stats; +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import org.junit.Ignore; import org.junit.Test; +import org.junit.runner.RunWith; import java.time.Duration; +import java.time.Instant; import de.dennisguse.opentracks.content.data.TestDataUtil; import de.dennisguse.opentracks.content.data.Track; +import de.dennisguse.opentracks.content.data.TrackPoint; import static org.junit.Assert.assertEquals; +@RunWith(AndroidJUnit4.class) public class TrackStatisticsUpdaterTest { + private static final int GPS_DISTANCE = 50; + @Test - public void addTrackPoint() { + public void addTrackPoint_TestingTrack() { // given TestDataUtil.TrackData data = TestDataUtil.createTestingTrack(new Track.Id(1)); // when - TrackStatisticsUpdater updater = new TrackStatisticsUpdater(); - data.trackPoints.forEach(it -> updater.addTrackPoint(it, 50)); + TrackStatisticsUpdater subject = new TrackStatisticsUpdater(); + data.trackPoints.forEach(it -> subject.addTrackPoint(it, GPS_DISTANCE)); // then - TrackStatistics statistics = updater.getTrackStatistics(); + TrackStatistics statistics = subject.getTrackStatistics(); assertEquals(85.35, statistics.getTotalDistance(), 0.01); assertEquals(Duration.ofMillis(13999), statistics.getTotalTime()); assertEquals(Duration.ofSeconds(6), statistics.getMovingTime()); @@ -35,4 +44,122 @@ public class TrackStatisticsUpdaterTest { assertEquals(14.226, statistics.getAverageMovingSpeed(), 0.01); assertEquals(6.566, statistics.getAverageSpeed(), 0.01); } + + @Test + public void addTrackPoint_distance_from_GPS_not_moving() { + // given + TrackStatisticsUpdater subject = new TrackStatisticsUpdater(); + + TrackPoint tp1 = new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.ofEpochMilli(1000)); + TrackPoint tp2 = new TrackPoint(0, 0, 5.0, Instant.ofEpochMilli(2000)); + TrackPoint tp3 = new TrackPoint(0.00001, 0, 5.0, Instant.ofEpochMilli(3000)); + + // when + subject.addTrackPoint(tp1, GPS_DISTANCE); + subject.addTrackPoint(tp2, GPS_DISTANCE); + subject.addTrackPoint(tp3, GPS_DISTANCE); + + // then + assertEquals(0, subject.getTrackStatistics().getTotalDistance(), 0.01); + } + + @Test + public void addTrackPoint_distance_from_GPS_moving() { + // given + TrackStatisticsUpdater subject = new TrackStatisticsUpdater(); + + TrackPoint tp1 = new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.ofEpochMilli(1000)); + TrackPoint tp2 = new TrackPoint(0, 0, 5.0, Instant.ofEpochMilli(2000)); + TrackPoint tp3 = new TrackPoint(0.00001, 0, 5.0, Instant.ofEpochMilli(3000)); + tp3.setSpeed(5f); + + // when + subject.addTrackPoint(tp1, GPS_DISTANCE); + subject.addTrackPoint(tp2, GPS_DISTANCE); + subject.addTrackPoint(tp3, GPS_DISTANCE); + + // then + assertEquals(1.10, subject.getTrackStatistics().getTotalDistance(), 0.01); + } + + @Test + public void addTrackPoint_distance_from_GPS_moving_and_sensor_moving() { + // given + TrackStatisticsUpdater subject = new TrackStatisticsUpdater(); + + TrackPoint tp1 = new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.ofEpochMilli(1000)); + TrackPoint tp2 = new TrackPoint(0, 0, 5.0, Instant.ofEpochMilli(2000)); + tp2.setSpeed(5f); + TrackPoint tp3 = new TrackPoint(0.001, 0, 5.0, Instant.ofEpochMilli(3000)); + tp2.setSpeed(5f); + TrackPoint tp4 = new TrackPoint(0.001, 0, 5.0, Instant.ofEpochMilli(4000)); + tp2.setSpeed(5f); + tp4.setSensorDistance(5f); + TrackPoint tp5 = new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, Instant.ofEpochMilli(5000)); + tp5.setSensorDistance(10f); + + // when + subject.addTrackPoint(tp1, GPS_DISTANCE); + subject.addTrackPoint(tp2, GPS_DISTANCE); + subject.addTrackPoint(tp3, GPS_DISTANCE); + + // then + assertEquals(110.57, subject.getTrackStatistics().getTotalDistance(), 0.01); + + // when + subject.addTrackPoint(tp4, GPS_DISTANCE); + subject.addTrackPoint(tp5, GPS_DISTANCE); + + // then + assertEquals(125.57, subject.getTrackStatistics().getTotalDistance(), 0.01); + } + + @Test + public void addTrackPoint_distance_from_GPS_not_moving_and_sensor_moving() { + // given + TrackStatisticsUpdater subject = new TrackStatisticsUpdater(); + + TrackPoint tp1 = new TrackPoint(TrackPoint.Type.SEGMENT_START_MANUAL, Instant.ofEpochMilli(1000)); + TrackPoint tp2 = new TrackPoint(0, 0, 5.0, Instant.ofEpochMilli(2000)); + TrackPoint tp3 = new TrackPoint(0.00001, 0, 5.0, Instant.ofEpochMilli(3000)); + TrackPoint tp4 = new TrackPoint(0.00001, 0, 5.0, Instant.ofEpochMilli(4000)); + tp4.setSensorDistance(5f); + TrackPoint tp5 = new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, Instant.ofEpochMilli(5000)); + tp5.setSensorDistance(10f); + + // when + subject.addTrackPoint(tp1, GPS_DISTANCE); + subject.addTrackPoint(tp2, GPS_DISTANCE); + subject.addTrackPoint(tp3, GPS_DISTANCE); + + // then + assertEquals(0, subject.getTrackStatistics().getTotalDistance(), 0.01); + + // when + subject.addTrackPoint(tp4, GPS_DISTANCE); + subject.addTrackPoint(tp5, GPS_DISTANCE); + + // then + assertEquals(15, subject.getTrackStatistics().getTotalDistance(), 0.01); + } + + @Ignore("TODO: create a concept ont to compute speed from GPS and sensor") + @Test + public void addTrackPoint_speed_from_GPS_not_moving() { + } + + @Ignore("TODO: create a concept ont to compute speed from GPS and sensor") + @Test + public void addTrackPoint_speed_from_GPS_moving() { + } + + @Ignore("TODO: create a concept ont to compute speed from GPS and sensor") + @Test + public void addTrackPoint_speed_from_GPS_not_moving_and_sensor_speed() { + } + + @Ignore("TODO: create a concept ont to compute speed from GPS and sensor") + @Test + public void addTrackPoint_speed_from_GPS_moving_and_sensor_speed() { + } } \ No newline at end of file diff --git a/src/androidTest/java/de/dennisguse/opentracks/util/BluetoothUtilsTest.java b/src/androidTest/java/de/dennisguse/opentracks/util/BluetoothUtilsTest.java index 5137e9f6d..58dcf0c6b 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/util/BluetoothUtilsTest.java +++ b/src/androidTest/java/de/dennisguse/opentracks/util/BluetoothUtilsTest.java @@ -47,7 +47,7 @@ public class BluetoothUtilsTest { // then assertEquals(200, sensor.getCadence().getCrankRevolutionsCount()); - assertNull(sensor.getSpeed()); + assertNull(sensor.getDistanceSpeed()); } @Test @@ -60,7 +60,7 @@ public class BluetoothUtilsTest { // then assertNull(sensor.getCadence()); - assertEquals(225, sensor.getSpeed().getWheelRevolutionsCount()); + assertEquals(225, sensor.getDistanceSpeed().getWheelRevolutionsCount()); } @Test @@ -73,7 +73,7 @@ public class BluetoothUtilsTest { // then assertEquals(200, sensor.getCadence().getCrankRevolutionsCount()); - assertEquals(225, sensor.getSpeed().getWheelRevolutionsCount()); + assertEquals(225, sensor.getDistanceSpeed().getWheelRevolutionsCount()); } @Test diff --git a/src/main/java/de/dennisguse/opentracks/content/data/TrackPoint.java b/src/main/java/de/dennisguse/opentracks/content/data/TrackPoint.java index e9660daaf..a3698093b 100644 --- a/src/main/java/de/dennisguse/opentracks/content/data/TrackPoint.java +++ b/src/main/java/de/dennisguse/opentracks/content/data/TrackPoint.java @@ -57,6 +57,7 @@ public class TrackPoint { private Double altitude_m; private Float speed_mps; private Float bearing; + private Float sensorDistance_m; public enum Type { SEGMENT_START_MANUAL(-2), //Start of a segment due to user interaction (start, resume) @@ -224,7 +225,7 @@ public class TrackPoint { return elevationGain != null; } - public Float getElevationGain() { + public float getElevationGain() { return elevationGain; } @@ -310,8 +311,12 @@ public class TrackPoint { this.accuracy = horizontalAccuracy; } - public float distanceTo(@NonNull TrackPoint dest) { - return getLocation().distanceTo(dest.getLocation()); + public float distanceToPrevious(@NonNull TrackPoint previous) { + if (hasSensorDistance()) { + return getSensorDistance(); + } + + return getLocation().distanceTo(previous.getLocation()); } public boolean fulfillsAccuracy(int poorAccuracy) { @@ -327,6 +332,18 @@ public class TrackPoint { } // Sensor data + public boolean hasSensorDistance() { + return sensorDistance_m != null; + } + + public Float getSensorDistance() { + return sensorDistance_m; + } + + public void setSensorDistance(Float distance_m) { + this.sensorDistance_m = distance_m; + } + public boolean hasSensorData() { return hasHeartRate() || hasCyclingCadence() || hasPower(); } @@ -347,7 +364,7 @@ public class TrackPoint { return cyclingCadence_rpm != null; } - public Float getCyclingCadence_rpm() { + public float getCyclingCadence_rpm() { return cyclingCadence_rpm; } @@ -359,7 +376,7 @@ public class TrackPoint { return power != null; } - public Float getPower() { + public float getPower() { return power; } diff --git a/src/main/java/de/dennisguse/opentracks/content/data/TrackPointsColumns.java b/src/main/java/de/dennisguse/opentracks/content/data/TrackPointsColumns.java index 6bf61e236..3de0e668e 100644 --- a/src/main/java/de/dennisguse/opentracks/content/data/TrackPointsColumns.java +++ b/src/main/java/de/dennisguse/opentracks/content/data/TrackPointsColumns.java @@ -50,6 +50,7 @@ public interface TrackPointsColumns extends BaseColumns { String BEARING = "bearing"; String SENSOR_HEARTRATE = "sensor_heartrate"; String SENSOR_CADENCE = "sensor_cadence"; + String SENSOR_DISTANCE = "sensor_distance"; //DISTANCE from previous TrackPoint String SENSOR_POWER = "sensor_power"; String ELEVATION_GAIN = "elevation_gain"; String ELEVATION_LOSS = "elevation_loss"; @@ -77,6 +78,7 @@ public interface TrackPointsColumns extends BaseColumns { + ELEVATION_GAIN + " FLOAT, " + ELEVATION_LOSS + " FLOAT, " + TYPE + " TEXT CHECK(type IN (-2, -1, 0, 1)), " + + SENSOR_DISTANCE + " FLOAT, " + "FOREIGN KEY (" + TRACKID + ") REFERENCES " + TracksColumns.TABLE_NAME + "(" + TracksColumns._ID + ") ON UPDATE CASCADE ON DELETE CASCADE" + ")"; diff --git a/src/main/java/de/dennisguse/opentracks/content/provider/CachedTrackPointsIndexes.java b/src/main/java/de/dennisguse/opentracks/content/provider/CachedTrackPointsIndexes.java index f391d7916..999be652e 100644 --- a/src/main/java/de/dennisguse/opentracks/content/provider/CachedTrackPointsIndexes.java +++ b/src/main/java/de/dennisguse/opentracks/content/provider/CachedTrackPointsIndexes.java @@ -19,6 +19,7 @@ class CachedTrackPointsIndexes { final int bearingIndex; final int sensorHeartRateIndex; final int sensorCadenceIndex; + final int sensorDistanceIndex; final int sensorPowerIndex; final int elevationGainIndex; final int elevationLossIndex; @@ -35,6 +36,7 @@ class CachedTrackPointsIndexes { bearingIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.BEARING); sensorHeartRateIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.SENSOR_HEARTRATE); sensorCadenceIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.SENSOR_CADENCE); + sensorDistanceIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.SENSOR_DISTANCE); sensorPowerIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.SENSOR_POWER); elevationGainIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.ELEVATION_GAIN); elevationLossIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.ELEVATION_LOSS); diff --git a/src/main/java/de/dennisguse/opentracks/content/provider/ContentProviderUtils.java b/src/main/java/de/dennisguse/opentracks/content/provider/ContentProviderUtils.java index c0b3e9728..dcd85c720 100644 --- a/src/main/java/de/dennisguse/opentracks/content/provider/ContentProviderUtils.java +++ b/src/main/java/de/dennisguse/opentracks/content/provider/ContentProviderUtils.java @@ -556,6 +556,9 @@ public class ContentProviderUtils { if (!cursor.isNull(indexes.sensorCadenceIndex)) { trackPoint.setCyclingCadence_rpm(cursor.getFloat(indexes.sensorCadenceIndex)); } + if (!cursor.isNull(indexes.sensorDistanceIndex)) { + trackPoint.setSensorDistance(cursor.getFloat(indexes.sensorDistanceIndex)); + } if (!cursor.isNull(indexes.sensorPowerIndex)) { trackPoint.setPower(cursor.getFloat(indexes.sensorPowerIndex)); } @@ -708,6 +711,9 @@ public class ContentProviderUtils { if (trackPoint.hasCyclingCadence()) { values.put(TrackPointsColumns.SENSOR_CADENCE, trackPoint.getCyclingCadence_rpm()); } + if (trackPoint.hasSensorDistance()) { + values.put(TrackPointsColumns.SENSOR_DISTANCE, trackPoint.getSensorDistance()); + } if (trackPoint.hasPower()) { values.put(TrackPointsColumns.SENSOR_POWER, trackPoint.getPower()); } diff --git a/src/main/java/de/dennisguse/opentracks/content/provider/CustomSQLiteOpenHelper.java b/src/main/java/de/dennisguse/opentracks/content/provider/CustomSQLiteOpenHelper.java index 84117e7c6..aee2b7549 100644 --- a/src/main/java/de/dennisguse/opentracks/content/provider/CustomSQLiteOpenHelper.java +++ b/src/main/java/de/dennisguse/opentracks/content/provider/CustomSQLiteOpenHelper.java @@ -25,7 +25,7 @@ public class CustomSQLiteOpenHelper extends SQLiteOpenHelper { private static final String TAG = CustomSQLiteOpenHelper.class.getSimpleName(); - private static final int DATABASE_VERSION = 30; + private static final int DATABASE_VERSION = 31; @VisibleForTesting public static final String DATABASE_NAME = "database.db"; @@ -82,6 +82,9 @@ public class CustomSQLiteOpenHelper extends SQLiteOpenHelper { case 30: upgradeFrom29to30(db); break; + case 31: + upgradeFrom30to31(db); + break; default: throw new RuntimeException("Not implemented: upgrade to " + toVersion); @@ -115,7 +118,9 @@ public class CustomSQLiteOpenHelper extends SQLiteOpenHelper { case 29: downgradeFrom30to29(db); break; - + case 30: + downgradeFrom31to30(db); + break; default: throw new RuntimeException("Not implemented: downgrade to " + toVersion); } @@ -394,4 +399,32 @@ public class CustomSQLiteOpenHelper extends SQLiteOpenHelper { db.setTransactionSuccessful(); db.endTransaction(); } + + /** + * Add distance column to TrackPoint. + */ + private void upgradeFrom30to31(SQLiteDatabase db) { + db.beginTransaction(); + + // TrackPoints + db.execSQL("ALTER TABLE trackpoints ADD COLUMN sensor_distance FLOAT"); + + db.setTransactionSuccessful(); + db.endTransaction(); + } + + private void downgradeFrom31to30(SQLiteDatabase db) { + db.beginTransaction(); + + // TrackPoints; identical to upgradeFrom27to28() + db.execSQL("ALTER TABLE trackpoints RENAME TO trackpoints_old"); + db.execSQL("CREATE TABLE trackpoints (_id INTEGER PRIMARY KEY AUTOINCREMENT, trackid INTEGER NOT NULL, longitude INTEGER, latitude INTEGER, time INTEGER, elevation FLOAT, accuracy FLOAT, speed FLOAT, bearing FLOAT, sensor_heartrate FLOAT, sensor_cadence FLOAT, sensor_power FLOAT, elevation_gain FLOAT, elevation_loss FLOAT, type TEXT CHECK(type IN (-2, -1, 0, 1)), FOREIGN KEY (trackid) REFERENCES tracks(_id) ON UPDATE CASCADE ON DELETE CASCADE)"); + db.execSQL("INSERT INTO trackpoints SELECT _id, trackid, longitude, latitude, time, elevation, accuracy, speed, bearing, sensor_heartrate, sensor_cadence, sensor_power, elevation_gain, elevation_gain, type FROM trackpoints_old"); + db.execSQL("DROP TABLE trackpoints_old"); + + db.execSQL("CREATE INDEX trackpoints_trackid_index ON trackpoints(trackid)"); + + db.setTransactionSuccessful(); + db.endTransaction(); + } } diff --git a/src/main/java/de/dennisguse/opentracks/content/sensor/SensorData.java b/src/main/java/de/dennisguse/opentracks/content/sensor/SensorData.java index 5caff36ff..a5e110e01 100644 --- a/src/main/java/de/dennisguse/opentracks/content/sensor/SensorData.java +++ b/src/main/java/de/dennisguse/opentracks/content/sensor/SensorData.java @@ -54,6 +54,12 @@ public abstract class SensorData { return value; } + /** + * Reset long term aggregated values (more than derived from previous SensorData). e.g. overall distance. + */ + public void reset() { + } + /** * Is the data recent considering the current time. */ @@ -61,4 +67,10 @@ public abstract class SensorData { return Instant.now() .isBefore(time.plus(BluetoothRemoteSensorManager.MAX_SENSOR_DATE_SET_AGE_MS)); } + + @NonNull + @Override + public String toString() { + return "sensorAddress='" + sensorAddress; + } } diff --git a/src/main/java/de/dennisguse/opentracks/content/sensor/SensorDataCycling.java b/src/main/java/de/dennisguse/opentracks/content/sensor/SensorDataCycling.java index 2fc466567..2c3a94e00 100644 --- a/src/main/java/de/dennisguse/opentracks/content/sensor/SensorDataCycling.java +++ b/src/main/java/de/dennisguse/opentracks/content/sensor/SensorDataCycling.java @@ -41,7 +41,7 @@ public final class SensorDataCycling { /** * Workaround for Wahoo CADENCE: provides speed instead of cadence */ - public Cadence(@NonNull SensorDataCycling.Speed speed) { + public Cadence(@NonNull DistanceSpeed speed) { this(speed.getSensorAddress(), speed.getSensorName(), speed.wheelRevolutionsCount, speed.wheelRevolutionsTime); } @@ -74,7 +74,7 @@ public final class SensorDataCycling { @NonNull @Override public String toString() { - return "cadence=" + value + " time=" + crankRevolutionsTime + " count=" + crankRevolutionsCount; + return super.toString() + " cadence=" + value + " time=" + crankRevolutionsTime + " count=" + crankRevolutionsCount; } @Override @@ -90,18 +90,18 @@ public final class SensorDataCycling { } } - public static class Speed extends SensorData { + public static class DistanceSpeed extends SensorData { private final Integer wheelRevolutionsCount; // UINT16 private final Integer wheelRevolutionsTime; // UINT16; 1/1024s - public Speed(String sensorAddress) { + public DistanceSpeed(String sensorAddress) { super(sensorAddress); this.wheelRevolutionsCount = null; this.wheelRevolutionsTime = null; } - public Speed(String sensorAddress, String sensorName, int wheelRevolutionsCount, int wheelRevolutionsTime) { + public DistanceSpeed(String sensorAddress, String sensorName, int wheelRevolutionsCount, int wheelRevolutionsTime) { super(sensorAddress, sensorName); this.wheelRevolutionsCount = wheelRevolutionsCount; this.wheelRevolutionsTime = wheelRevolutionsTime; @@ -119,7 +119,7 @@ public final class SensorDataCycling { return wheelRevolutionsTime; } - public void compute(Speed previous, int wheel_circumference_mm) { + public void compute(DistanceSpeed previous, int wheel_circumference_mm) { if (hasData() && previous != null && previous.hasData()) { float timeDiff_ms = UintUtils.diff(wheelRevolutionsTime, previous.wheelRevolutionsTime, UintUtils.UINT16_MAX) / 1024f * UnitConversions.S_TO_MS; if (timeDiff_ms <= 0) { @@ -128,42 +128,76 @@ public final class SensorDataCycling { } else { long wheelDiff = UintUtils.diff(wheelRevolutionsCount, previous.wheelRevolutionsCount, UintUtils.UINT16_MAX); double timeDiff_s = timeDiff_ms * UnitConversions.MS_TO_S; - value = (float) (wheelDiff * wheel_circumference_mm * UnitConversions.MM_TO_M / timeDiff_s); + float distance_m = (float) (wheelDiff * wheel_circumference_mm * UnitConversions.MM_TO_M); + float distance_overall_m = distance_m; + if (previous.hasValue()) { + distance_overall_m += previous.getValue().distance_overall_m; + } + float speed_mps = (float) (distance_m / timeDiff_s); + value = new Data(distance_m, distance_overall_m, speed_mps); } } } + @Override + public void reset() { + if (value != null) { + value = new Data(value.distance_m, 0, value.speed_mps); + } + } + @NonNull @Override public String toString() { - return "speed=" + value + " time=" + wheelRevolutionsTime + " count=" + wheelRevolutionsCount; + return super.toString() + " data=" + value + " time=" + wheelRevolutionsTime + " count=" + wheelRevolutionsCount; } @Override public boolean equals(@Nullable Object obj) { - if (!(obj instanceof Speed)) return false; + if (!(obj instanceof DistanceSpeed)) return false; - Speed comp = (Speed) obj; + DistanceSpeed comp = (DistanceSpeed) obj; if (hasData() && comp.hasData() == hasData()) { return getWheelRevolutionsCount() == comp.getWheelRevolutionsCount() && getWheelRevolutionsTime() == comp.getWheelRevolutionsTime(); } else { return false; } } + + public static class Data { + public final float distance_m; + public final float distance_overall_m; + public final float speed_mps; + + private Data(float distance_m, float distance_overall_m, float speed_mps) { + this.distance_m = distance_m; + this.distance_overall_m = distance_overall_m; + this.speed_mps = speed_mps; + } + + @Override + public String toString() { + return "Data{" + + "distance_m=" + distance_m + + ", distance_overall_m=" + distance_overall_m + + ", speed_mps=" + speed_mps + + '}'; + } + } } - public static class CadenceAndSpeed extends SensorData> { + public static class CadenceAndSpeed extends SensorData> { - public CadenceAndSpeed(String sensorAddress, String sensorName, @NonNull Cadence cadence, @NonNull Speed speed) { + public CadenceAndSpeed(String sensorAddress, String sensorName, @Nullable Cadence cadence, @Nullable DistanceSpeed distanceSpeed) { super(sensorAddress, sensorName); - this.value = new Pair<>(cadence, speed); + this.value = new Pair<>(cadence, distanceSpeed); } public Cadence getCadence() { return this.value != null ? this.value.first : null; } - public Speed getSpeed() { + public DistanceSpeed getDistanceSpeed() { return this.value != null ? this.value.second : null; } } diff --git a/src/main/java/de/dennisguse/opentracks/content/sensor/SensorDataCyclingPower.java b/src/main/java/de/dennisguse/opentracks/content/sensor/SensorDataCyclingPower.java index 2fa263a1f..3662bcd3d 100644 --- a/src/main/java/de/dennisguse/opentracks/content/sensor/SensorDataCyclingPower.java +++ b/src/main/java/de/dennisguse/opentracks/content/sensor/SensorDataCyclingPower.java @@ -16,6 +16,6 @@ public class SensorDataCyclingPower extends SensorData { @NonNull @Override public String toString() { - return "power=" + value; + return super.toString() + " power=" + value; } } diff --git a/src/main/java/de/dennisguse/opentracks/content/sensor/SensorDataHeartRate.java b/src/main/java/de/dennisguse/opentracks/content/sensor/SensorDataHeartRate.java index b1b4db565..d1e7c1604 100644 --- a/src/main/java/de/dennisguse/opentracks/content/sensor/SensorDataHeartRate.java +++ b/src/main/java/de/dennisguse/opentracks/content/sensor/SensorDataHeartRate.java @@ -16,6 +16,6 @@ public class SensorDataHeartRate extends SensorData { @NonNull @Override public String toString() { - return "heart=" + value; + return super.toString() + " heart=" + value; } } diff --git a/src/main/java/de/dennisguse/opentracks/content/sensor/SensorDataSet.java b/src/main/java/de/dennisguse/opentracks/content/sensor/SensorDataSet.java index 7469e68c5..cc054e663 100644 --- a/src/main/java/de/dennisguse/opentracks/content/sensor/SensorDataSet.java +++ b/src/main/java/de/dennisguse/opentracks/content/sensor/SensorDataSet.java @@ -10,7 +10,7 @@ public final class SensorDataSet { private SensorDataCycling.Cadence cyclingCadence; - private SensorDataCycling.Speed cyclingSpeed; + private SensorDataCycling.DistanceSpeed cyclingDistanceSpeed; private SensorDataCyclingPower cyclingPower; @@ -25,8 +25,8 @@ public final class SensorDataSet { return cyclingCadence; } - public SensorDataCycling.Speed getCyclingSpeed() { - return cyclingSpeed; + public SensorDataCycling.DistanceSpeed getCyclingDistanceSpeed() { + return cyclingDistanceSpeed; } public SensorDataCyclingPower getCyclingPower() { @@ -44,7 +44,7 @@ public final class SensorDataSet { public void clear() { this.heartRate = null; this.cyclingCadence = null; - this.cyclingSpeed = null; + this.cyclingDistanceSpeed = null; this.cyclingPower = null; } @@ -57,8 +57,9 @@ public final class SensorDataSet { trackPoint.setCyclingCadence_rpm(cyclingCadence.getValue()); } - if (cyclingSpeed != null && cyclingSpeed.hasValue()) { - trackPoint.setSpeed(cyclingSpeed.getValue()); + if (cyclingDistanceSpeed != null && cyclingDistanceSpeed.hasValue()) { + trackPoint.setSensorDistance(cyclingDistanceSpeed.getValue().distance_overall_m); + trackPoint.setSpeed(cyclingDistanceSpeed.getValue().speed_mps); } if (cyclingPower != null && cyclingPower.hasValue()) { @@ -66,12 +67,19 @@ public final class SensorDataSet { } } + public void reset() { + if (heartRate != null) heartRate.reset(); + if (cyclingCadence != null) cyclingCadence.reset(); + if (cyclingDistanceSpeed != null) cyclingDistanceSpeed.reset(); + if (cyclingPower != null) cyclingPower.reset(); + } + @NonNull @Override public String toString() { return (getHeartRate() != null ? "" + getHeartRate() : "") + (getCyclingCadence() != null ? " " + getCyclingCadence() : "") - + (getCyclingSpeed() != null ? " " + getCyclingSpeed() : "") + + (getCyclingDistanceSpeed() != null ? " " + getCyclingDistanceSpeed() : "") + (getCyclingPower() != null ? " " + getCyclingPower() : ""); } @@ -85,8 +93,8 @@ public final class SensorDataSet { this.cyclingCadence = (SensorDataCycling.Cadence) data; return; } - if (type instanceof SensorDataCycling.Speed) { - this.cyclingSpeed = (SensorDataCycling.Speed) data; + if (type instanceof SensorDataCycling.DistanceSpeed) { + this.cyclingDistanceSpeed = (SensorDataCycling.DistanceSpeed) data; return; } diff --git a/src/main/java/de/dennisguse/opentracks/fragments/StatisticsRecordingFragment.java b/src/main/java/de/dennisguse/opentracks/fragments/StatisticsRecordingFragment.java index ddfff026d..254c1fa8c 100644 --- a/src/main/java/de/dennisguse/opentracks/fragments/StatisticsRecordingFragment.java +++ b/src/main/java/de/dennisguse/opentracks/fragments/StatisticsRecordingFragment.java @@ -62,6 +62,7 @@ public class StatisticsRecordingFragment extends Fragment implements TrackDataLi private TrackRecordingServiceConnection trackRecordingServiceConnection = new TrackRecordingServiceConnection(); private TrackPoint lastTrackPoint; + //TODO Should not be nullable private TrackStatistics lastTrackStatistics; private String category = ""; @@ -366,29 +367,26 @@ public class StatisticsRecordingFragment extends Fragment implements TrackDataLi } private void setSpeedSensorData(SensorDataSet sensorDataSet) { - if (sensorDataSet != null && sensorDataSet.getCyclingSpeed() != null) { - SensorDataCycling.Speed data = sensorDataSet.getCyclingSpeed(); + if (sensorDataSet != null && sensorDataSet.getCyclingDistanceSpeed() != null) { + SensorDataCycling.DistanceSpeed data = sensorDataSet.getCyclingDistanceSpeed(); if (data.hasValue() && data.isRecent()) { - setSpeed(data.getValue()); + setTotalDistance(data.getValue().distance_overall_m); + setSpeed(data.getValue().speed_mps); + } + if (data.hasValue() && data.isRecent()) { + setSpeed(data.getValue().speed_mps); } } } private void updateStats() { - String trackIconValue = TrackIconUtils.getIconValue(getContext(), category); - // Set total distance - { - double totalDistance = lastTrackStatistics == null ? Double.NaN : lastTrackStatistics.getTotalDistance(); - Pair parts = StringUtils.getDistanceParts(getContext(), totalDistance, preferenceMetricUnits); - - viewBinding.statsDistanceValue.setText(parts.first); - viewBinding.statsDistanceUnit.setText(parts.second); - } + setTotalDistance(0); // Set activity type { + String trackIconValue = TrackIconUtils.getIconValue(getContext(), category); viewBinding.statsActivityTypeIcon.setEnabled(isSelectedTrackRecording()); viewBinding.statsActivityTypeIcon.setImageResource(TrackIconUtils.getIconDrawable(trackIconValue)); } @@ -479,6 +477,14 @@ public class StatisticsRecordingFragment extends Fragment implements TrackDataLi } } + private void setTotalDistance(double sensorDistanceSinceLastTrackpoint) { + double totalDistance = lastTrackStatistics != null ? (lastTrackStatistics.getTotalDistance() + sensorDistanceSinceLastTrackpoint) : Double.NaN; + Pair parts = StringUtils.getDistanceParts(getContext(), totalDistance, preferenceMetricUnits); + + viewBinding.statsDistanceValue.setText(parts.first); + viewBinding.statsDistanceUnit.setText(parts.second); + } + private void setSpeed(double speed) { viewBinding.statsSpeedLabel.setText(preferenceReportSpeed ? R.string.stats_speed : R.string.stats_pace); 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 54ed0f635..2150dbf6a 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 @@ -49,6 +49,7 @@ public class GPXTrackExporter implements TrackExporter { private static final NumberFormat ELEVATION_FORMAT = NumberFormat.getInstance(Locale.US); private static final NumberFormat COORDINATE_FORMAT = NumberFormat.getInstance(Locale.US); private static final NumberFormat SPEED_FORMAT = NumberFormat.getInstance(Locale.US); + private static final NumberFormat DISTANCE_FORMAT = NumberFormat.getInstance(Locale.US); private static final NumberFormat HEARTRATE_FORMAT = NumberFormat.getInstance(Locale.US); private static final NumberFormat CADENCE_FORMAT = NumberFormat.getInstance(Locale.US); private static final NumberFormat POWER_FORMAT = NumberFormat.getInstance(Locale.US); @@ -312,6 +313,10 @@ public class GPXTrackExporter implements TrackExporter { printWriter.println("" + ELEVATION_FORMAT.format(trackPoint.getElevationLoss()) + ""); } + if (trackPoint.hasSensorDistance()) { + printWriter.println("" + DISTANCE_FORMAT.format(trackPoint.getElevationLoss()) + ""); + } + printWriter.println(""); } diff --git a/src/main/java/de/dennisguse/opentracks/io/file/exporter/KMLTrackExporter.java b/src/main/java/de/dennisguse/opentracks/io/file/exporter/KMLTrackExporter.java index 0de60e743..5c8664a7f 100644 --- a/src/main/java/de/dennisguse/opentracks/io/file/exporter/KMLTrackExporter.java +++ b/src/main/java/de/dennisguse/opentracks/io/file/exporter/KMLTrackExporter.java @@ -57,6 +57,7 @@ public class KMLTrackExporter implements TrackExporter { private static final String SCHEMA_ID = "schema"; public static final String EXTENDED_DATA_TYPE_SPEED = "speed"; + public static final String EXTENDED_DATA_TYPE_DISTANCE = "distance"; public static final String EXTENDED_DATA_TYPE_CADENCE = "cadence"; public static final String EXTENDED_DATA_TYPE_HEART_RATE = "heart_rate"; public static final String EXTENDED_DATA_TYPE_POWER = "power"; @@ -74,6 +75,7 @@ public class KMLTrackExporter implements TrackExporter { private PrintWriter printWriter; private final List speedList = new ArrayList<>(); + private final List distanceList = new ArrayList<>(); private final List powerList = new ArrayList<>(); private final List cadenceList = new ArrayList<>(); private final List heartRateList = new ArrayList<>(); @@ -338,6 +340,7 @@ public class KMLTrackExporter implements TrackExporter { if (printWriter != null) { printWriter.println(""); speedList.clear(); + distanceList.clear(); powerList.clear(); cadenceList.clear(); heartRateList.clear(); @@ -355,6 +358,9 @@ public class KMLTrackExporter implements TrackExporter { writeSimpleArrayData(speedList, EXTENDED_DATA_TYPE_SPEED); } if (exportSensorData) { + if (distanceList.stream().anyMatch(Objects::nonNull)) { + writeSimpleArrayData(distanceList, EXTENDED_DATA_TYPE_DISTANCE); + } if (powerList.stream().anyMatch(Objects::nonNull)) { writeSimpleArrayData(powerList, EXTENDED_DATA_TYPE_POWER); } @@ -392,6 +398,7 @@ public class KMLTrackExporter implements TrackExporter { speedList.add(trackPoint.hasSpeed() ? trackPoint.getSpeed() : null); if (exportSensorData) { + distanceList.add(trackPoint.hasSensorDistance() ? trackPoint.getSensorDistance() : null); heartRateList.add(trackPoint.hasHeartRate() ? trackPoint.getHeartRate_bpm() : null); cadenceList.add(trackPoint.hasCyclingCadence() ? trackPoint.getCyclingCadence_rpm() : null); powerList.add(trackPoint.hasPower() ? trackPoint.getPower() : null); diff --git a/src/main/java/de/dennisguse/opentracks/io/file/importer/AbstractFileTrackImporter.java b/src/main/java/de/dennisguse/opentracks/io/file/importer/AbstractFileTrackImporter.java index 1b6443f95..360d5f8f5 100644 --- a/src/main/java/de/dennisguse/opentracks/io/file/importer/AbstractFileTrackImporter.java +++ b/src/main/java/de/dennisguse/opentracks/io/file/importer/AbstractFileTrackImporter.java @@ -91,6 +91,7 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track protected String uuid; protected String gain; protected String loss; + protected String distance; // The current track data private TrackData trackData; @@ -141,9 +142,6 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track } } - /** - * On file end. - */ protected void onFileEnd() { // Add markers to the last imported track int size = trackIds.size(); @@ -376,7 +374,7 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track * GPS points tend to have some inherent imprecision, speed and bearing will likely be off, so the statistics for things like max speed will also be off. */ if (trackPoint.hasLocation() && trackData.lastLocationInCurrentSegment.hasLocation()) { - float speed = trackData.lastLocationInCurrentSegment.distanceTo(trackPoint) / timeDifference.toMillis(); + float speed = trackData.lastLocationInCurrentSegment.distanceToPrevious(trackPoint) / timeDifference.toMillis(); trackPoint.setSpeed(speed); } } @@ -385,7 +383,7 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track trackPoint.setBearing(trackData.lastLocationInCurrentSegment.bearingTo(trackPoint)); long maxRecordingDistance = PreferencesUtils.getMaxRecordingDistance(sharedPreferences, context); //TODO Should only be read once! - double distanceToLastTrackLocation = trackPoint.distanceTo(trackData.lastLocationInCurrentSegment); + double distanceToLastTrackLocation = trackPoint.distanceToPrevious(trackData.lastLocationInCurrentSegment); if (distanceToLastTrackLocation > maxRecordingDistance) { trackPoint.setType(TrackPoint.Type.SEGMENT_START_AUTOMATIC); } @@ -505,6 +503,13 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track throw new ParsingException(createErrorMessage(String.format(Locale.US, "Unable to parse elevation loss: %s", loss)), e); } } + if (distance != null) { + try { + trackPoint.setSensorDistance(Float.parseFloat(distance)); + } catch (Exception e) { + throw new ParsingException(createErrorMessage(String.format(Locale.US, "Unable to parse distance: %s", distance)), e); + } + } return trackPoint; } diff --git a/src/main/java/de/dennisguse/opentracks/io/file/importer/GpxFileTrackImporter.java b/src/main/java/de/dennisguse/opentracks/io/file/importer/GpxFileTrackImporter.java index 72dab53b3..cd1b42e4c 100644 --- a/src/main/java/de/dennisguse/opentracks/io/file/importer/GpxFileTrackImporter.java +++ b/src/main/java/de/dennisguse/opentracks/io/file/importer/GpxFileTrackImporter.java @@ -63,6 +63,7 @@ public class GpxFileTrackImporter extends AbstractFileTrackImporter { private static final String TAG_EXTENSION_GAIN = "opentracks:gain"; private static final String TAG_EXTENSION_LOSS = "opentracks:loss"; + private static final String TAG_EXTENSION_DISTANCE = "opentracks:distance"; /** * Constructor. @@ -179,6 +180,11 @@ public class GpxFileTrackImporter extends AbstractFileTrackImporter { loss = content.trim(); } break; + case TAG_EXTENSION_DISTANCE: + if (content != null) { + distance = content.trim(); + } + break; } // Reset element content diff --git a/src/main/java/de/dennisguse/opentracks/io/file/importer/KmlFileTrackImporter.java b/src/main/java/de/dennisguse/opentracks/io/file/importer/KmlFileTrackImporter.java index 84544fcf3..b2786c1f6 100644 --- a/src/main/java/de/dennisguse/opentracks/io/file/importer/KmlFileTrackImporter.java +++ b/src/main/java/de/dennisguse/opentracks/io/file/importer/KmlFileTrackImporter.java @@ -66,6 +66,7 @@ public class KmlFileTrackImporter extends AbstractFileTrackImporter { private String extendedDataType; private final ArrayList trackPoints = new ArrayList<>(); private final ArrayList speedList = new ArrayList<>(); + private final ArrayList distanceList = new ArrayList<>(); private final ArrayList cadenceList = new ArrayList<>(); private final ArrayList heartRateList = new ArrayList<>(); private final ArrayList powerList = new ArrayList<>(); @@ -216,6 +217,7 @@ public class KmlFileTrackImporter extends AbstractFileTrackImporter { super.onTrackSegmentStart(); trackPoints.clear(); speedList.clear(); + distanceList.clear(); heartRateList.clear(); cadenceList.clear(); powerList.clear(); @@ -232,6 +234,9 @@ public class KmlFileTrackImporter extends AbstractFileTrackImporter { if (i < speedList.size()) { trackPoint.setSpeed(speedList.get(i)); } + if (i < distanceList.size()) { + trackPoint.setSensorDistance(distanceList.get(i)); + } if (i < heartRateList.size()) { trackPoint.setHeartRate_bpm(heartRateList.get(i)); } @@ -310,6 +315,9 @@ public class KmlFileTrackImporter extends AbstractFileTrackImporter { case KMLTrackExporter.EXTENDED_DATA_TYPE_SPEED: speedList.add(value); break; + case KMLTrackExporter.EXTENDED_DATA_TYPE_DISTANCE: + distanceList.add(value); + break; case KMLTrackExporter.EXTENDED_DATA_TYPE_POWER: powerList.add(value); break; diff --git a/src/main/java/de/dennisguse/opentracks/services/TrackRecordingService.java b/src/main/java/de/dennisguse/opentracks/services/TrackRecordingService.java index 3d58063b6..4be7ad436 100644 --- a/src/main/java/de/dennisguse/opentracks/services/TrackRecordingService.java +++ b/src/main/java/de/dennisguse/opentracks/services/TrackRecordingService.java @@ -571,7 +571,7 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand return; } - double distanceToLastTrackLocation = trackPoint.distanceTo(lastValidTrackPoint); + double distanceToLastTrackLocation = trackPoint.distanceToPrevious(lastValidTrackPoint); if (distanceToLastTrackLocation > maxRecordingDistance) { insertTrackPointIfNewer(track, lastTrackPoint); @@ -664,6 +664,10 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand trackPoint.setElevationLoss(elevationSumManager.getElevationLoss_m()); elevationSumManager.reset(); } + if (remoteSensorManager != null) { + fillWithSensorDataSet(trackPoint); + remoteSensorManager.reset(); + } contentProviderUtils.insertTrackPoint(trackPoint, track.getId()); trackStatisticsUpdater.addTrackPoint(trackPoint, recordingDistanceInterval); diff --git a/src/main/java/de/dennisguse/opentracks/services/sensors/BluetoothConnectionManager.java b/src/main/java/de/dennisguse/opentracks/services/sensors/BluetoothConnectionManager.java index 671ec09ad..c518a9c59 100644 --- a/src/main/java/de/dennisguse/opentracks/services/sensors/BluetoothConnectionManager.java +++ b/src/main/java/de/dennisguse/opentracks/services/sensors/BluetoothConnectionManager.java @@ -101,11 +101,11 @@ public abstract class BluetoothConnectionManager { @Override public void onCharacteristicChanged(BluetoothGatt gatt, @NonNull BluetoothGattCharacteristic characteristic) { - String sensorName = gatt.getDevice().getName(); - Log.d(TAG, "Received data from " + sensorName); + Log.d(TAG, "Received data from " + gatt.getDevice().getAddress()); - SensorData sensorData = parsePayload(sensorName, gatt.getDevice().getAddress(), characteristic); + SensorData sensorData = parsePayload(gatt.getDevice().getName(), gatt.getDevice().getAddress(), characteristic); if (sensorData != null) { + Log.d(TAG, "Decoded data from " + gatt.getDevice().getAddress() + ": " + sensorData); observer.onChanged(sensorData); } } @@ -125,7 +125,7 @@ public abstract class BluetoothConnectionManager { Log.d(TAG, "Connecting to: " + device); bluetoothGatt = device.connectGatt(context, true, connectCallback); - SensorData sensorData = createEmptySensorData(bluetoothGatt.getDevice().getAddress()); + SensorData sensorData = createEmptySensorData(bluetoothGatt.getDevice().getAddress()); observer.onChanged(sensorData); } @@ -148,12 +148,12 @@ public abstract class BluetoothConnectionManager { return address.equals(bluetoothGatt.getDevice().getAddress()); } - protected abstract SensorData createEmptySensorData(String address); + protected abstract SensorData createEmptySensorData(String address); /** * @return null if data could not be parsed. */ - protected abstract SensorData parsePayload(String sensorName, String address, BluetoothGattCharacteristic characteristic); + protected abstract SensorData parsePayload(String sensorName, String address, BluetoothGattCharacteristic characteristic); public static class HeartRate extends BluetoothConnectionManager { @@ -162,7 +162,7 @@ public abstract class BluetoothConnectionManager { } @Override - protected SensorData createEmptySensorData(String address) { + protected SensorDataHeartRate createEmptySensorData(String address) { return new SensorDataHeartRate(address); } @@ -181,7 +181,7 @@ public abstract class BluetoothConnectionManager { } @Override - protected SensorData createEmptySensorData(String address) { + protected SensorDataCycling.Cadence createEmptySensorData(String address) { return new SensorDataCycling.Cadence(address); } @@ -197,30 +197,30 @@ public abstract class BluetoothConnectionManager { } //Workaround for Wahoo CADENCE: this sensor reports speed (instead of cadence) - if (cadenceAndSpeed.getSpeed() != null) { - return new SensorDataCycling.Cadence(cadenceAndSpeed.getSpeed()); + if (cadenceAndSpeed.getDistanceSpeed() != null) { + return new SensorDataCycling.Cadence(cadenceAndSpeed.getDistanceSpeed()); } return null; } } - public static class CyclingSpeed extends BluetoothConnectionManager { + public static class CyclingDistanceSpeed extends BluetoothConnectionManager { - CyclingSpeed(SensorDataObserver observer) { + CyclingDistanceSpeed(SensorDataObserver observer) { super(BluetoothUtils.CYCLING_SPEED_CADENCE_SERVICE_UUID, BluetoothUtils.CYCLING_SPEED_CADENCE_MEASUREMENT_CHAR_UUID, observer); } @Override - protected SensorData createEmptySensorData(String address) { - return new SensorDataCycling.Speed(address); + protected SensorDataCycling.DistanceSpeed createEmptySensorData(String address) { + return new SensorDataCycling.DistanceSpeed(address); } @Override - protected SensorDataCycling.Speed parsePayload(String sensorName, String address, BluetoothGattCharacteristic characteristic) { + protected SensorDataCycling.DistanceSpeed parsePayload(String sensorName, String address, BluetoothGattCharacteristic characteristic) { SensorDataCycling.CadenceAndSpeed cadenceAndSpeed = BluetoothUtils.parseCyclingCrankAndWheel(address, sensorName, characteristic); if (cadenceAndSpeed != null) { - return cadenceAndSpeed.getSpeed(); + return cadenceAndSpeed.getDistanceSpeed(); } return null; } @@ -233,7 +233,7 @@ public abstract class BluetoothConnectionManager { } @Override - protected SensorData createEmptySensorData(String address) { + protected SensorDataCyclingPower createEmptySensorData(String address) { return new SensorDataCyclingPower(address); } @@ -247,8 +247,8 @@ public abstract class BluetoothConnectionManager { interface SensorDataObserver { - void onChanged(SensorData sensorData); + void onChanged(SensorData sensorData); - void onDisconnecting(SensorData sensorData); + void onDisconnecting(SensorData sensorData); } } diff --git a/src/main/java/de/dennisguse/opentracks/services/sensors/BluetoothRemoteSensorManager.java b/src/main/java/de/dennisguse/opentracks/services/sensors/BluetoothRemoteSensorManager.java index 0af815b6b..748162fe0 100644 --- a/src/main/java/de/dennisguse/opentracks/services/sensors/BluetoothRemoteSensorManager.java +++ b/src/main/java/de/dennisguse/opentracks/services/sensors/BluetoothRemoteSensorManager.java @@ -53,13 +53,15 @@ public class BluetoothRemoteSensorManager implements BluetoothConnectionManager. private final BluetoothAdapter bluetoothAdapter; private final Context context; - private final SharedPreferences sharedPreferences; private boolean started = false; + private final SharedPreferences sharedPreferences; + private int preferenceWheelCircumference; + private final BluetoothConnectionManager.HeartRate heartRate = new BluetoothConnectionManager.HeartRate(this); private final BluetoothConnectionManager.CyclingCadence cyclingCadence = new BluetoothConnectionManager.CyclingCadence(this); - private final BluetoothConnectionManager.CyclingSpeed cyclingSpeed = new BluetoothConnectionManager.CyclingSpeed(this); + private final BluetoothConnectionManager.CyclingDistanceSpeed cyclingSpeed = new BluetoothConnectionManager.CyclingDistanceSpeed(this); private final BluetoothConnectionManager.CyclingPower cyclingPower = new BluetoothConnectionManager.CyclingPower(this); private final SensorDataSet sensorDataSet = new SensorDataSet(); @@ -90,6 +92,11 @@ public class BluetoothRemoteSensorManager implements BluetoothConnectionManager. connect(cyclingPower, address); } + + if (PreferencesUtils.isKey(context, R.string.settings_sensor_bluetooth_cycling_power_key, key)) { + preferenceWheelCircumference = PreferencesUtils.getWheelCircumference(sharedPreferences, context); + + } } }; @@ -155,11 +162,15 @@ public class BluetoothRemoteSensorManager implements BluetoothConnectionManager. return sensorDataSet; } + public void reset() { + sensorDataSet.reset(); + } + @Override - public synchronized void onChanged(SensorData sensorData) { + public synchronized void onChanged(SensorData sensorData) { if (sensorData instanceof SensorDataCycling.Cadence) { SensorDataCycling.Cadence previous = sensorDataSet.getCyclingCadence(); - Log.d(TAG, "previous " + previous + "; current" + sensorData); + Log.d(TAG, "Previous: " + previous + "; current: " + sensorData); if (sensorData.equals(previous)) { Log.d(TAG, "onChanged: cadence data repeated."); @@ -167,21 +178,21 @@ public class BluetoothRemoteSensorManager implements BluetoothConnectionManager. } ((SensorDataCycling.Cadence) sensorData).compute(previous); } - if (sensorData instanceof SensorDataCycling.Speed) { - SensorDataCycling.Speed previous = sensorDataSet.getCyclingSpeed(); - Log.d(TAG, "previous " + previous + "; current" + sensorData); + if (sensorData instanceof SensorDataCycling.DistanceSpeed) { + SensorDataCycling.DistanceSpeed previous = sensorDataSet.getCyclingDistanceSpeed(); + Log.d(TAG, "Previous: " + previous + "; Current" + sensorData); if (sensorData.equals(previous)) { Log.d(TAG, "onChanged: speed data repeated."); return; } - ((SensorDataCycling.Speed) sensorData).compute(previous, PreferencesUtils.getWheelCircumference(sharedPreferences, context)); + ((SensorDataCycling.DistanceSpeed) sensorData).compute(previous, PreferencesUtils.getWheelCircumference(sharedPreferences, context)); } sensorDataSet.set(sensorData); } @Override - public void onDisconnecting(SensorData sensorData) { + public void onDisconnecting(SensorData sensorData) { sensorDataSet.remove(sensorData); } } diff --git a/src/main/java/de/dennisguse/opentracks/stats/TrackStatisticsUpdater.java b/src/main/java/de/dennisguse/opentracks/stats/TrackStatisticsUpdater.java index f5b7f5f33..0926a2080 100644 --- a/src/main/java/de/dennisguse/opentracks/stats/TrackStatisticsUpdater.java +++ b/src/main/java/de/dennisguse/opentracks/stats/TrackStatisticsUpdater.java @@ -103,10 +103,15 @@ public class TrackStatisticsUpdater { /** * Adds a trackPoint. * - * @param trackPoint the trackPoint - * @param minRecordingDistance the min recording distance + * @param trackPoint the trackPoint + * @param minGPSDistance the min recording distance */ - public void addTrackPoint(TrackPoint trackPoint, int minRecordingDistance) { + public void addTrackPoint(TrackPoint trackPoint, int minGPSDistance) { + internalAddTrackPoint(trackPoint, minGPSDistance); + Log.d(TAG, this.toString()); + } + + private void internalAddTrackPoint(TrackPoint trackPoint, int minGPSDistance) { if (!trackInitialized) { trackStatistics.setStartTime(trackPoint.getTime()); trackInitialized = true; @@ -120,19 +125,26 @@ public class TrackStatisticsUpdater { currentSegment.setStopTime(trackPoint.getTime()); currentSegment.setTotalTime(Duration.between(currentSegment.getStartTime(), trackPoint.getTime())); - if (trackPoint.isSegmentStart() || trackPoint.isSegmentEnd()) { - if (trackPoint.isSegmentEnd()) { - if (lastTrackPoint != null && lastMovingTrackPoint != null && lastTrackPoint != lastMovingTrackPoint) { - currentSegment.addTotalDistance(lastMovingTrackPoint.distanceTo(lastTrackPoint)); - } - } - trackStatistics.merge(currentSegment); - currentSegment.reset(trackPoint.getTime()); + if (trackPoint.isSegmentStart()) { + reset(trackPoint); + return; + } - lastTrackPoint = null; - lastMovingTrackPoint = null; - elevationBuffer_m.reset(); - speedBuffer_ms.reset(); + // Process sensor data + if (trackPoint.hasElevationGain()) { + currentSegment.addTotalElevationGain(trackPoint.getElevationGain()); + } + + if (trackPoint.hasElevationLoss()) { + currentSegment.addTotalElevationLoss(trackPoint.getElevationLoss()); + } + + if (trackPoint.hasSensorDistance()) { + currentSegment.addTotalDistance(trackPoint.getSensorDistance()); + } + + if (trackPoint.isSegmentEnd()) { + reset(trackPoint); return; } @@ -141,39 +153,30 @@ public class TrackStatisticsUpdater { updateAbsoluteElevation(trackPoint.getAltitude()); } - //Get elevation gain - if (trackPoint.hasElevationGain()) { - currentSegment.addTotalElevationGain(trackPoint.getElevationGain()); - Log.d(TAG, "elevation gain: " + trackPoint.getElevationGain()); - } - - //Get elevation loss - if (trackPoint.hasElevationLoss()) { - currentSegment.addTotalElevationLoss(trackPoint.getElevationLoss()); - Log.d(TAG, "elevation loss: " + trackPoint.getElevationLoss()); - } - if (lastTrackPoint == null || lastMovingTrackPoint == null) { lastTrackPoint = trackPoint; lastMovingTrackPoint = trackPoint; return; } - double movingDistance = lastMovingTrackPoint.distanceTo(trackPoint); - if (movingDistance < minRecordingDistance && !trackPoint.isMoving()) { - speedBuffer_ms.reset(); - lastTrackPoint = trackPoint; - return; + if (!trackPoint.hasSensorDistance()) { + // GPS-based distance/speed + float movingDistance = lastMovingTrackPoint.distanceToPrevious(trackPoint); + if (movingDistance < minGPSDistance && !trackPoint.isMoving()) { + speedBuffer_ms.reset(); + lastTrackPoint = trackPoint; + return; + } + // Update total distance + currentSegment.addTotalDistance(movingDistance); } + Duration movingTime = Duration.between(lastTrackPoint.getTime(), trackPoint.getTime()); if (movingTime.isNegative()) { lastTrackPoint = trackPoint; return; } - // Update total distance - currentSegment.addTotalDistance(movingDistance); - // Update moving time currentSegment.addMovingTime(movingTime); @@ -186,6 +189,16 @@ public class TrackStatisticsUpdater { lastMovingTrackPoint = trackPoint; } + private void reset(TrackPoint trackPoint) { + trackStatistics.merge(currentSegment); + currentSegment.reset(trackPoint.getTime()); + + lastTrackPoint = null; + lastMovingTrackPoint = null; + elevationBuffer_m.reset(); + speedBuffer_ms.reset(); + } + public void addTrackPoint(TrackPointIterator iterator, int minRecordingDistance) { while (iterator.hasNext()) { TrackPoint location = iterator.next(); @@ -224,20 +237,14 @@ public class TrackStatisticsUpdater { /** * Updates an elevation reading. - * - * @param elevation the elevation - * @return the difference */ - @VisibleForTesting - private double updateAbsoluteElevation(double elevation) { + private void updateAbsoluteElevation(double elevation) { // Update elevation using the smoothed average double oldAverage = elevationBuffer_m.getAverage(); elevationBuffer_m.setNext(elevation); double newAverage = elevationBuffer_m.getAverage(); currentSegment.updateElevationExtremities(newAverage); - - return newAverage - oldAverage; } private boolean isValidSpeed(@NonNull TrackPoint trackPoint, @NonNull TrackPoint lastTrackPoint) { @@ -267,4 +274,11 @@ public class TrackStatisticsUpdater { return true; } + + @Override + public String toString() { + return "TrackStatisticsUpdater{" + + "trackStatistics=" + trackStatistics + + '}'; + } } diff --git a/src/main/java/de/dennisguse/opentracks/util/BluetoothUtils.java b/src/main/java/de/dennisguse/opentracks/util/BluetoothUtils.java index 5150ec5a0..730c6c965 100644 --- a/src/main/java/de/dennisguse/opentracks/util/BluetoothUtils.java +++ b/src/main/java/de/dennisguse/opentracks/util/BluetoothUtils.java @@ -126,12 +126,12 @@ public class BluetoothUtils { cadence = new SensorDataCycling.Cadence(address, sensorName, crankCount, crankTime); } - SensorDataCycling.Speed speed = null; + SensorDataCycling.DistanceSpeed speed = null; if (hasWheel && valueLength - index >= 4) { int wheelCount = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, index); index += 2; int wheelTime = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT16, index); // 1/1024s - speed = new SensorDataCycling.Speed(address, sensorName, wheelCount, wheelTime); + speed = new SensorDataCycling.DistanceSpeed(address, sensorName, wheelCount, wheelTime); } return new SensorDataCycling.CadenceAndSpeed(address, sensorName, cadence, speed); diff --git a/src/main/java/de/dennisguse/opentracks/util/PreferencesUtils.java b/src/main/java/de/dennisguse/opentracks/util/PreferencesUtils.java index 1cfe05fd4..4537de37d 100644 --- a/src/main/java/de/dennisguse/opentracks/util/PreferencesUtils.java +++ b/src/main/java/de/dennisguse/opentracks/util/PreferencesUtils.java @@ -42,6 +42,7 @@ public class PreferencesUtils { private PreferencesUtils() { } + @Deprecated //Should only be used to get a sharedPreference for more than one interaction! public static SharedPreferences getSharedPreferences(Context context) { return PreferenceManager.getDefaultSharedPreferences(context); } @@ -102,7 +103,6 @@ public class PreferencesUtils { editor.apply(); } - private static int getInt(SharedPreferences sharedPreferences, Context context, int keyId, int defaultValue) { try { return sharedPreferences.getInt(getKey(context, keyId), defaultValue); @@ -206,7 +206,6 @@ public class PreferencesUtils { return getString(sharedPreferences, context, R.string.settings_sensor_bluetooth_cycling_power_key, getBluetoothSensorAddressNone(context)); } - public static boolean shouldShowStatsOnLockscreen(SharedPreferences sharedPreferences, Context context) { final boolean STATS_SHOW_ON_LOCKSCREEN_DEFAULT = context.getResources().getBoolean(R.bool.stats_show_on_lockscreen_while_recording_default); return getBoolean(sharedPreferences, context, R.string.stats_show_on_lockscreen_while_recording_key, STATS_SHOW_ON_LOCKSCREEN_DEFAULT); diff --git a/src/main/java/de/dennisguse/opentracks/viewmodels/IntervalStatistics.java b/src/main/java/de/dennisguse/opentracks/viewmodels/IntervalStatistics.java index a461f5ee2..b73ff0421 100644 --- a/src/main/java/de/dennisguse/opentracks/viewmodels/IntervalStatistics.java +++ b/src/main/java/de/dennisguse/opentracks/viewmodels/IntervalStatistics.java @@ -33,7 +33,7 @@ public class IntervalStatistics { TrackPoint trackPoint = trackPoints.get(i); if (trackPoint.hasLocation() && prevTrackPoint.hasLocation()) { - interval.distance_m += prevTrackPoint.distanceTo(trackPoint); + interval.distance_m += prevTrackPoint.distanceToPrevious(trackPoint); interval.time = interval.time.plus(Duration.between(prevTrackPoint.getTime(), trackPoint.getTime())); interval.gain_m += trackPoint.hasElevationGain() ? trackPoint.getElevationGain() : 0; interval.loss_m += trackPoint.hasElevationLoss() ? trackPoint.getElevationLoss() : 0;