From 6f9c788965eae153643af8d41aa704c1570a47ea Mon Sep 17 00:00:00 2001 From: Dennis Guse Date: Fri, 9 Apr 2021 19:59:44 +0200 Subject: [PATCH] Value objects distance and speed. #679 --- .../content/DescriptionGeneratorTest.java | 22 ++- .../opentracks/content/data/TestDataUtil.java | 20 +- .../content/data/TestSensorDataUtil.java | 2 +- .../CustomContentProviderUtilsTest.java | 8 +- .../content/sensor/SensorDataCyclingTest.java | 8 +- .../fragments/ChartFragmentTest.java | 17 +- .../io/file/importer/ExportImportTest.java | 18 +- ...cordingServiceNotificationManagerTest.java | 7 +- .../services/TrackRecordingServiceTest.java | 4 +- .../opentracks/stats/TrackStatisticsTest.java | 34 ++-- .../stats/TrackStatisticsUpdaterTest.java | 40 ++-- .../util/AnnouncementUtilsTest.java | 10 +- .../opentracks/util/StringUtilsTest.java | 34 ++-- .../viewmodels/AggregatedStatisticsTest.java | 72 +++---- .../viewmodels/IntervalStatisticsTest.java | 187 +++++++++--------- .../opentracks/TrackListActivity.java | 3 +- .../adapters/IntervalStatisticsAdapter.java | 10 +- .../opentracks/chart/ChartPoint.java | 13 +- .../opentracks/chart/ChartView.java | 4 +- .../content/DescriptionGenerator.java | 17 +- .../opentracks/content/data/Distance.java | 116 +++++++++++ .../opentracks/content/data/Marker.java | 19 +- .../opentracks/content/data/Speed.java | 145 ++++++++++++++ .../opentracks/content/data/TrackPoint.java | 29 ++- .../provider/ContentProviderUtils.java | 26 +-- .../content/sensor/SensorDataCycling.java | 42 ++-- .../content/sensor/SensorDataSet.java | 4 +- .../opentracks/fragments/ChartFragment.java | 8 +- .../fragments/StatisticsRecordedFragment.java | 14 +- .../StatisticsRecordingFragment.java | 24 +-- .../io/file/exporter/GPXTrackExporter.java | 2 +- .../io/file/exporter/KMLTrackExporter.java | 4 +- .../importer/AbstractFileTrackImporter.java | 23 +-- .../file/importer/KmlFileTrackImporter.java | 10 +- .../services/TrackRecordingService.java | 15 +- ...ckRecordingServiceNotificationManager.java | 3 +- .../services/handlers/GpsStatus.java | 4 +- .../services/handlers/LocationHandler.java | 2 +- .../services/tasks/PeriodicTaskExecutor.java | 14 +- .../opentracks/settings/PreferenceHelper.java | 2 +- .../opentracks/stats/TrackStatistics.java | 62 +++--- .../stats/TrackStatisticsUpdater.java | 54 +++-- .../opentracks/util/AnnouncementUtils.java | 23 ++- .../opentracks/util/PreferencesUtils.java | 15 +- .../opentracks/util/StringUtils.java | 81 +++----- .../opentracks/util/UnitConversions.java | 2 +- .../viewmodels/IntervalStatistics.java | 2 +- 47 files changed, 773 insertions(+), 502 deletions(-) create mode 100644 src/main/java/de/dennisguse/opentracks/content/data/Distance.java create mode 100644 src/main/java/de/dennisguse/opentracks/content/data/Speed.java diff --git a/src/androidTest/java/de/dennisguse/opentracks/content/DescriptionGeneratorTest.java b/src/androidTest/java/de/dennisguse/opentracks/content/DescriptionGeneratorTest.java index cec90d240..b85623873 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/content/DescriptionGeneratorTest.java +++ b/src/androidTest/java/de/dennisguse/opentracks/content/DescriptionGeneratorTest.java @@ -29,6 +29,8 @@ import java.time.Duration; import java.time.Instant; import de.dennisguse.opentracks.R; +import de.dennisguse.opentracks.content.data.Distance; +import de.dennisguse.opentracks.content.data.Speed; import de.dennisguse.opentracks.content.data.Track; import de.dennisguse.opentracks.stats.TrackStatistics; import de.dennisguse.opentracks.util.StringUtils; @@ -60,10 +62,10 @@ public class DescriptionGeneratorTest { public void testGenerateTrackDescription() { Track track = new Track(); TrackStatistics stats = new TrackStatistics(); - stats.setTotalDistance(20000); + stats.setTotalDistance(Distance.of(20000)); stats.setTotalTime(Duration.ofMillis(600000)); stats.setMovingTime(Duration.ofMillis(300000)); - stats.setMaxSpeed(100); + stats.setMaxSpeed(Speed.of(100)); stats.setMaxAltitude(550); stats.setMinAltitude(-500); stats.setTotalAltitudeGain(6000f); @@ -98,10 +100,10 @@ public class DescriptionGeneratorTest { public void testGenerateTrackDescriptionWithoutMaxMinAltitude() { Track track = new Track(); TrackStatistics stats = new TrackStatistics(); - stats.setTotalDistance(20000); + stats.setTotalDistance(Distance.of(20000)); stats.setTotalTime(Duration.ofMillis(600000)); stats.setMovingTime(Duration.ofMillis(300000)); - stats.setMaxSpeed(100); + stats.setMaxSpeed(Speed.of(100)); stats.setMaxAltitude(Double.POSITIVE_INFINITY); stats.setMinAltitude(Double.NEGATIVE_INFINITY); stats.setTotalAltitudeGain(6000f); @@ -132,12 +134,12 @@ public class DescriptionGeneratorTest { /** - * Tests {@link DescriptionGenerator#writeDistance(double, StringBuilder, int, String)}. + * Tests {@link DescriptionGenerator#writeDistance(Distance, StringBuilder, int, String)}. */ @Test public void testWriteDistance() { StringBuilder builder = new StringBuilder(); - descriptionGenerator.writeDistance(1100, builder, R.string.description_total_distance, "
"); + descriptionGenerator.writeDistance(Distance.of(1100), builder, R.string.description_total_distance, "
"); assertEquals("Total distance: 1.10 km (0.7 mi)
", builder.toString()); } @@ -152,12 +154,12 @@ public class DescriptionGeneratorTest { } /** - * Tests {@link DescriptionGenerator#writeSpeed(double, StringBuilder, int, String)}. + * Tests {@link DescriptionGenerator#writeSpeed(Speed, StringBuilder, int, String)}. */ @Test public void testWriteSpeed() { StringBuilder builder = new StringBuilder(); - descriptionGenerator.writeSpeed(1.1, builder, R.string.description_average_speed, "\n"); + descriptionGenerator.writeSpeed(Speed.of(1.1), builder, R.string.description_average_speed, "\n"); assertEquals("Average speed: 3.96 km/h (2.5 mi/h)\n", builder.toString()); } @@ -172,12 +174,12 @@ public class DescriptionGeneratorTest { } /** - * Tests {@link DescriptionGenerator#writePace(double, StringBuilder, int, String)}. + * Tests {@link DescriptionGenerator#writePace(Speed, StringBuilder, int, String)}. */ @Test public void testWritePace() { StringBuilder builder = new StringBuilder(); - descriptionGenerator.writePace(1.1, builder, R.string.description_average_pace_in_minute, "\n"); + descriptionGenerator.writePace(Speed.of(1.1), builder, R.string.description_average_pace_in_minute, "\n"); assertEquals("Average pace: 15:09 min/km (24:23 min/mi)\n", builder.toString()); } } diff --git a/src/androidTest/java/de/dennisguse/opentracks/content/data/TestDataUtil.java b/src/androidTest/java/de/dennisguse/opentracks/content/data/TestDataUtil.java index 06bf960de..bc9f9521f 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/content/data/TestDataUtil.java +++ b/src/androidTest/java/de/dennisguse/opentracks/content/data/TestDataUtil.java @@ -13,6 +13,7 @@ import java.util.List; import de.dennisguse.opentracks.content.provider.ContentProviderUtils; import de.dennisguse.opentracks.content.provider.TrackPointIterator; +import de.dennisguse.opentracks.stats.TrackStatistics; import de.dennisguse.opentracks.util.FileUtils; public class TestDataUtil { @@ -75,10 +76,14 @@ public class TestDataUtil { TrackPoint.createSegmentEndWithTime(Instant.ofEpochSecond(i++ + 1)) ); + //TODO Use TrackStatisticsUpdater + TrackStatistics stats = new TrackStatistics(); + stats.setTotalDistance(Distance.of(0)); + stats.setTotalTime(Duration.ofMillis(0)); List markers = List.of( - new Marker("Marker 1", "Marker description 1", "Marker category 3", "", trackId, 0.0, Duration.ofMillis(0), trackPoints.get(1), null), - new Marker("Marker 2", "Marker description 2", "Marker category 3", "", trackId, 0.0, Duration.ofMillis(0), trackPoints.get(4), null), - new Marker("Marker 3", "Marker description 3", "Marker category 3", "", trackId, 0.0, Duration.ofMillis(0), trackPoints.get(5), null) + new Marker("Marker 1", "Marker description 1", "Marker category 3", "", trackId, stats, trackPoints.get(1), null), + new Marker("Marker 2", "Marker description 2", "Marker category 3", "", trackId, stats, trackPoints.get(4), null), + new Marker("Marker 3", "Marker description 3", "Marker category 3", "", trackId, stats, trackPoints.get(5), null) ); return new TrackData(track, trackPoints, markers); @@ -117,7 +122,7 @@ public class TestDataUtil { trackPoint.setAccuracy((float) i / 100.0f); trackPoint.setAltitude(i * ALTITUDE_INTERVAL); trackPoint.setTime(Instant.ofEpochSecond(i + 1)); - trackPoint.setSpeed(5f + (i / 10f)); + trackPoint.setSpeed(Speed.of(5f + (i / 10f))); trackPoint.setHeartRate_bpm(100f + i); trackPoint.setCyclingCadence_rpm(300f + i); @@ -150,7 +155,12 @@ public class TestDataUtil { Uri photoUri = FileUtils.getUriForFile(context, dstFile); String photoUrl = photoUri.toString(); - return new Marker("Marker name", "Marker description", "Marker category", "", trackId, 0.0, Duration.ofMillis(0), trackPoint, photoUrl); + //TODO Use TrackStatisticsUpdater + TrackStatistics stats = new TrackStatistics(); + stats.setTotalDistance(Distance.of(0)); + stats.setTotalTime(Duration.ofMillis(0)); + + return new Marker("Marker name", "Marker description", "Marker category", "", trackId, stats, trackPoint, photoUrl); } public static List getTrackPoints(ContentProviderUtils contentProviderUtils, Track.Id trackId) { diff --git a/src/androidTest/java/de/dennisguse/opentracks/content/data/TestSensorDataUtil.java b/src/androidTest/java/de/dennisguse/opentracks/content/data/TestSensorDataUtil.java index a5179b556..e64566ccf 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/content/data/TestSensorDataUtil.java +++ b/src/androidTest/java/de/dennisguse/opentracks/content/data/TestSensorDataUtil.java @@ -22,7 +22,7 @@ public class TestSensorDataUtil { tp.setAccuracy(1f); tp.setAltitude(1f); tp.setTime(time); - tp.setSpeed(5f + (i / 10f)); + tp.setSpeed(Speed.of(5f + (i / 10f))); tp.setAltitudeGain(3f); tp.setAltitudeLoss(3f); trackPointList.add(tp); diff --git a/src/androidTest/java/de/dennisguse/opentracks/content/provider/CustomContentProviderUtilsTest.java b/src/androidTest/java/de/dennisguse/opentracks/content/provider/CustomContentProviderUtilsTest.java index 75b283805..88f44917a 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/content/provider/CustomContentProviderUtilsTest.java +++ b/src/androidTest/java/de/dennisguse/opentracks/content/provider/CustomContentProviderUtilsTest.java @@ -42,8 +42,10 @@ import java.util.Random; import java.util.UUID; import java.util.stream.Collectors; +import de.dennisguse.opentracks.content.data.Distance; import de.dennisguse.opentracks.content.data.Marker; import de.dennisguse.opentracks.content.data.MarkerColumns; +import de.dennisguse.opentracks.content.data.Speed; import de.dennisguse.opentracks.content.data.TestDataUtil; import de.dennisguse.opentracks.content.data.TestSensorDataUtil; import de.dennisguse.opentracks.content.data.Track; @@ -428,9 +430,9 @@ public class CustomContentProviderUtilsTest { statistics.setStopTime(Instant.ofEpochMilli(2500)); statistics.setTotalTime(Duration.ofMillis(1500)); statistics.setMovingTime(Duration.ofMillis(700)); - statistics.setTotalDistance(750.0); + statistics.setTotalDistance(Distance.of(750.0)); statistics.setTotalAltitudeGain(50.0f); - statistics.setMaxSpeed(60.0); + statistics.setMaxSpeed(Speed.of(60.0)); statistics.setMaxAltitude(1250.0); statistics.setMinAltitude(1200.0); @@ -807,7 +809,7 @@ public class CustomContentProviderUtilsTest { assertEquals(longitude, trackPoint.getLongitude(), 0.01); assertEquals(latitude, trackPoint.getLatitude(), 0.01); assertEquals(time, trackPoint.getTime().toEpochMilli()); - assertEquals(speed, trackPoint.getSpeed(), 0.01); + assertEquals(speed, trackPoint.getSpeed().toMPS(), 0.01); assertFalse(trackPoint.hasHeartRate()); } 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 491133f83..ae9af596e 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/content/sensor/SensorDataCyclingTest.java +++ b/src/androidTest/java/de/dennisguse/opentracks/content/sensor/SensorDataCyclingTest.java @@ -103,8 +103,8 @@ public class SensorDataCyclingTest { current.compute(previous, 2150); // then - assertEquals(1.20, current.getValue().distance_m, 2150); - assertEquals(1.20, current.getValue().speed_mps, 0.01); + assertEquals(2.15, current.getValue().distance.toM(), 0.01); + assertEquals(1.20, current.getValue().speed.toMPS(), 0.01); } @Test @@ -117,8 +117,8 @@ public class SensorDataCyclingTest { current.compute(previous, 2000); // then - assertEquals(1.20, current.getValue().distance_m, 2000); - assertEquals(2, current.getValue().speed_mps, 0.01); + assertEquals(2, current.getValue().distance.toM(), 0.01); + assertEquals(2, current.getValue().speed.toMPS(), 0.01); } @Test diff --git a/src/androidTest/java/de/dennisguse/opentracks/fragments/ChartFragmentTest.java b/src/androidTest/java/de/dennisguse/opentracks/fragments/ChartFragmentTest.java index 05df65c5b..503b93f88 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/fragments/ChartFragmentTest.java +++ b/src/androidTest/java/de/dennisguse/opentracks/fragments/ChartFragmentTest.java @@ -33,6 +33,8 @@ import java.time.Instant; import de.dennisguse.opentracks.chart.ChartPoint; import de.dennisguse.opentracks.chart.ChartView; +import de.dennisguse.opentracks.content.data.Distance; +import de.dennisguse.opentracks.content.data.Speed; import de.dennisguse.opentracks.content.data.TrackPoint; import de.dennisguse.opentracks.util.UnitConversions; @@ -66,6 +68,7 @@ public class ChartFragmentTest { boolean chartByDistance = false; chartFragment = ChartFragment.newInstance(chartByDistance); chartFragment.setChartView(new ChartView(ApplicationProvider.getApplicationContext(), chartByDistance)); + chartFragment.setRecordingDistanceInterval(Distance.of(50)); } /** @@ -245,7 +248,7 @@ public class ChartFragmentTest { * It will not be filled in to the speed buffer. */ TrackPoint trackPoint1 = TrackStubUtils.createDefaultTrackPoint(); - trackPoint1.setSpeed(128.5f); + trackPoint1.setSpeed(Speed.of(128.5f)); ChartPoint point = chartFragment.createPendingPoint(trackPoint1); assertEquals(0.0, point.getSpeed(), 0.01); @@ -259,7 +262,7 @@ public class ChartFragmentTest { * Add a time span here to make sure the second point is valid, the value 222 here is doesn't matter. */ trackPoint2.setTime(trackPoint1.getTime().plusMillis(222)); - trackPoint2.setSpeed(130f); + trackPoint2.setSpeed(Speed.of(130f)); point = chartFragment.createPendingPoint(trackPoint2); assertEquals(130.0 * UnitConversions.MPS_TO_KMH, point.getSpeed(), 0.01); } @@ -273,7 +276,7 @@ public class ChartFragmentTest { // First data point is not added to the speed buffer TrackPoint trackPoint1 = TrackStubUtils.createDefaultTrackPoint(); - trackPoint1.setSpeed(100.0f); + trackPoint1.setSpeed(Speed.of(100.0f)); ChartPoint point = chartFragment.createPendingPoint(trackPoint1); assertEquals(0.0, point.getSpeed(), 0.01); @@ -284,7 +287,7 @@ public class ChartFragmentTest { * Speed is valid if: speedDifference > Constants.MAX_ACCELERATION * timeDifference speedDifference = 102 -100 timeDifference = 222 */ trackPoint2.setTime(trackPoint2.getTime().plusMillis(222)); - trackPoint2.setSpeed(102f); + trackPoint2.setSpeed(Speed.of(102f)); point = chartFragment.createPendingPoint(trackPoint2); assertEquals(102.0 * UnitConversions.MPS_TO_KMH * UnitConversions.KM_TO_MI, point.getSpeed(), 0.01); } @@ -298,7 +301,7 @@ public class ChartFragmentTest { // First data point is not added to the speed buffer TrackPoint trackPoint1 = TrackStubUtils.createDefaultTrackPoint(); - trackPoint1.setSpeed(100.0f); + trackPoint1.setSpeed(Speed.of(100.0f)); ChartPoint point = chartFragment.createPendingPoint(trackPoint1); assertEquals(0.0, point.getSpeed(), 0.01); @@ -309,7 +312,7 @@ public class ChartFragmentTest { * Speed is valid if: speedDifference > Constants.MAX_ACCELERATION * timeDifference speedDifference = 102 -100 timeDifference = 222 */ trackPoint2.setTime(trackPoint2.getTime().plusMillis(222)); - trackPoint2.setSpeed(102f); + trackPoint2.setSpeed(Speed.of(102f)); point = chartFragment.createPendingPoint(trackPoint2); assertEquals(HOURS_PER_UNIT / (102.0 * UnitConversions.MPS_TO_KMH), point.getPace(), 0.01); } @@ -321,7 +324,7 @@ public class ChartFragmentTest { public void testCreatePendingPoint_pace_zeroSpeed() { chartFragment.setReportSpeed(false); TrackPoint trackPoint = TrackStubUtils.createDefaultTrackPoint(); - trackPoint.setSpeed(0f); + trackPoint.setSpeed(Speed.of(0f)); ChartPoint point = chartFragment.createPendingPoint(trackPoint); assertEquals(0.0, point.getPace(), 0.01); } 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 9e846337e..a6342a26c 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 @@ -32,7 +32,9 @@ import java.util.concurrent.TimeoutException; import java.util.stream.Collectors; import de.dennisguse.opentracks.R; +import de.dennisguse.opentracks.content.data.Distance; import de.dennisguse.opentracks.content.data.Marker; +import de.dennisguse.opentracks.content.data.Speed; import de.dennisguse.opentracks.content.data.TestDataUtil; import de.dennisguse.opentracks.content.data.Track; import de.dennisguse.opentracks.content.data.TrackPoint; @@ -353,7 +355,7 @@ public class ExportImportTest { } assertEquals(trackPoint.hasSpeed(), importedTrackPoint.hasSpeed()); if (trackPoint.hasSpeed()) { - assertEquals(trackPoint.getSpeed(), importedTrackPoint.getSpeed(), 0.001); + assertEquals(trackPoint.getSpeed().toMPS(), importedTrackPoint.getSpeed().toMPS(), 0.001); } assertEquals(trackPoint.hasAltitude(), importedTrackPoint.hasAltitude()); if (trackPoint.hasAltitude()) { @@ -381,7 +383,7 @@ public class ExportImportTest { assertEquals(trackPoint.getAltitudeLoss(), importedTrackPoint.getAltitudeLoss(), 0.01); } if (verifyDistance) { - assertEquals(trackPoint.getSensorDistance(), importedTrackPoint.getSensorDistance(), 0.01); + assertEquals(trackPoint.getSensorDistance(), importedTrackPoint.getSensorDistance()); } } } @@ -405,13 +407,13 @@ public class ExportImportTest { // Distance if (verifyDistance) { - assertEquals(trackStatistics.getTotalDistance(), importedTrackStatistics.getTotalDistance(), 0.01); + assertEquals(trackStatistics.getTotalDistance(), importedTrackStatistics.getTotalDistance()); } // Speed - assertEquals(trackStatistics.getMaxSpeed(), importedTrackStatistics.getMaxSpeed(), 0.01); - assertEquals(trackStatistics.getAverageSpeed(), importedTrackStatistics.getAverageSpeed(), 0.01); - assertEquals(trackStatistics.getAverageMovingSpeed(), importedTrackStatistics.getAverageMovingSpeed(), 0.01); + assertEquals(trackStatistics.getMaxSpeed(), importedTrackStatistics.getMaxSpeed()); + assertEquals(trackStatistics.getAverageSpeed(), importedTrackStatistics.getAverageSpeed()); + assertEquals(trackStatistics.getAverageMovingSpeed(), importedTrackStatistics.getAverageMovingSpeed()); } // Altitude @@ -429,12 +431,12 @@ public class ExportImportTest { private static TrackPoint createTrackPoint(long time, double latitude, double longitude, float accuracy, float speed, float altitude, float altitudeGain, 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.setSpeed(Speed.of(speed)); tp.setHeartRate_bpm(heartRate); tp.setCyclingCadence_rpm(cyclingCadence); tp.setPower(power); tp.setAltitudeGain(altitudeGain); - tp.setSensorDistance(distance); + tp.setSensorDistance(Distance.of(distance)); return tp; } } \ No newline at end of file diff --git a/src/androidTest/java/de/dennisguse/opentracks/services/TrackRecordingServiceNotificationManagerTest.java b/src/androidTest/java/de/dennisguse/opentracks/services/TrackRecordingServiceNotificationManagerTest.java index c97abc6d7..ddf91794d 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/services/TrackRecordingServiceNotificationManagerTest.java +++ b/src/androidTest/java/de/dennisguse/opentracks/services/TrackRecordingServiceNotificationManagerTest.java @@ -11,11 +11,12 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; +import de.dennisguse.opentracks.content.data.Distance; +import de.dennisguse.opentracks.content.data.Speed; import de.dennisguse.opentracks.content.data.TrackPoint; import de.dennisguse.opentracks.stats.TrackStatistics; import static org.mockito.ArgumentMatchers.anyBoolean; -import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -41,8 +42,8 @@ public class TrackRecordingServiceNotificationManagerTest { public void updateLocation_triggersAlertOnlyOnFirstInaccurateLocation() { when(trackPointMock.hasAccuracy()).thenReturn(true); when(trackPointMock.getAccuracy()).thenReturn(999f); - when(trackStatisticsMock.getTotalDistance()).thenReturn(0d); - when(notificationCompatBuilder.setContentText(anyString())).thenReturn(notificationCompatBuilder); + when(trackPointMock.getSpeed()).thenReturn(Speed.of(0)); + when(trackStatisticsMock.getTotalDistance()).thenReturn(Distance.of(0)); when(notificationCompatBuilder.setOnlyAlertOnce(anyBoolean())).thenReturn(notificationCompatBuilder); TrackRecordingServiceNotificationManager subject = new TrackRecordingServiceNotificationManager(notificationManager, notificationCompatBuilder); diff --git a/src/androidTest/java/de/dennisguse/opentracks/services/TrackRecordingServiceTest.java b/src/androidTest/java/de/dennisguse/opentracks/services/TrackRecordingServiceTest.java index f1bfccf30..f23e9af1d 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/services/TrackRecordingServiceTest.java +++ b/src/androidTest/java/de/dennisguse/opentracks/services/TrackRecordingServiceTest.java @@ -360,12 +360,12 @@ public class TrackRecordingServiceTest { Marker.Id markerId = service.insertMarker(null, null, null, null); // then - assertNotEquals(-1L, markerId); + assertNotEquals(new Marker.Id(-1L), markerId); Marker wpt = contentProviderUtils.getMarker(markerId); assertEquals(context.getString(R.string.marker_icon_url), wpt.getIcon()); assertEquals(context.getString(R.string.marker_name_format, 1), wpt.getName()); assertEquals(trackId, wpt.getTrackId()); - assertEquals(0.0, wpt.getLength(), 0.01); + assertEquals(0.0, wpt.getLength().toM(), 0.01); assertNotNull(wpt.getLocation()); service.endCurrentTrack(); diff --git a/src/androidTest/java/de/dennisguse/opentracks/stats/TrackStatisticsTest.java b/src/androidTest/java/de/dennisguse/opentracks/stats/TrackStatisticsTest.java index 7054c05e8..27df9a6b0 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/stats/TrackStatisticsTest.java +++ b/src/androidTest/java/de/dennisguse/opentracks/stats/TrackStatisticsTest.java @@ -15,14 +15,18 @@ */ package de.dennisguse.opentracks.stats; +import androidx.test.ext.junit.runners.AndroidJUnit4; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; import java.time.Duration; import java.time.Instant; +import de.dennisguse.opentracks.content.data.Distance; +import de.dennisguse.opentracks.content.data.Speed; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; @@ -32,7 +36,7 @@ import static org.junit.Assert.assertNull; * * @author Rodrigo Damazio */ -@RunWith(JUnit4.class) +@RunWith(AndroidJUnit4.class) public class TrackStatisticsTest { private TrackStatistics statistics; @@ -60,9 +64,9 @@ public class TrackStatisticsTest { assertNull(statistics.getTotalAltitudeLoss()); assertEquals(Double.NEGATIVE_INFINITY, statistics.getMaxAltitude(), 0.0); assertEquals(Double.POSITIVE_INFINITY, statistics.getMinAltitude(), 0.0); - assertEquals(0.0, statistics.getMaxSpeed(), 0.0); - assertEquals(0.0, statistics.getAverageSpeed(), 0.0); - assertEquals(0.0, statistics.getAverageMovingSpeed(), 0.0); + assertEquals(0.0, statistics.getMaxSpeed().toMPS(), 0.0); + assertEquals(0.0, statistics.getAverageSpeed().toMPS(), 0.0); + assertEquals(0.0, statistics.getAverageMovingSpeed().toMPS(), 0.0); } @Test @@ -77,12 +81,12 @@ public class TrackStatisticsTest { statistics2.setTotalTime(Duration.ofMillis(1000)); // Result: 1500+1000 statistics.setMovingTime(Duration.ofMillis(700)); statistics2.setMovingTime(Duration.ofMillis(600)); // Result: 700+600 - statistics.setTotalDistance(750.0); - statistics2.setTotalDistance(350.0); // Result: 750+350 + statistics.setTotalDistance(Distance.of(750.0)); + statistics2.setTotalDistance(Distance.of(350.0)); // Result: 750+350 statistics.setTotalAltitudeGain(50.0f); statistics2.setTotalAltitudeGain(850.0f); // Result: 850+50 - statistics.setMaxSpeed(60.0); // Resulting max speed - statistics2.setMaxSpeed(30.0); + statistics.setMaxSpeed(Speed.of(60.0)); // Resulting max speed + statistics2.setMaxSpeed(Speed.of(30.0)); statistics.setMaxAltitude(1250.0); statistics.setMinAltitude(1200.0); // Resulting min altitude statistics2.setMaxAltitude(3575.0); // Resulting max altitude @@ -96,24 +100,24 @@ public class TrackStatisticsTest { assertEquals(Instant.ofEpochMilli(4000), statistics.getStopTime()); assertEquals(Duration.ofMillis(2500), statistics.getTotalTime()); assertEquals(Duration.ofMillis(1300), statistics.getMovingTime()); - assertEquals(1100.0, statistics.getTotalDistance(), 0.001); + assertEquals(1100.0, statistics.getTotalDistance().toM(), 0.001); assertEquals(900.0, statistics.getTotalAltitudeGain(), 0.001); - assertEquals(statistics.getTotalDistance() / statistics.getMovingTime().getSeconds(), statistics.getMaxSpeed(), 0.001); + assertEquals(Speed.of(statistics.getTotalDistance(), statistics.getMovingTime()).toMPS(), statistics.getMaxSpeed().toMPS(), 0.001); assertEquals(1200.0, statistics.getMinAltitude(), 0.001); assertEquals(3575.0, statistics.getMaxAltitude(), 0.001); } @Test public void testGetAverageSpeed() { - statistics.setTotalDistance(1000.0); + statistics.setTotalDistance(Distance.of(1000.0)); statistics.setTotalTime(Duration.ofMillis(50000)); - assertEquals(20.0, statistics.getAverageSpeed(), 0.001); + assertEquals(20.0, statistics.getAverageSpeed().toMPS(), 0.001); } @Test public void testGetAverageMovingSpeed() { - statistics.setTotalDistance(1000.0); + statistics.setTotalDistance(Distance.of(1000.0)); statistics.setMovingTime(Duration.ofMillis(20000)); - assertEquals(50.0, statistics.getAverageMovingSpeed(), 0.001); + assertEquals(50.0, statistics.getAverageMovingSpeed().toMPS(), 0.001); } } diff --git a/src/androidTest/java/de/dennisguse/opentracks/stats/TrackStatisticsUpdaterTest.java b/src/androidTest/java/de/dennisguse/opentracks/stats/TrackStatisticsUpdaterTest.java index eaec8da61..330226168 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/stats/TrackStatisticsUpdaterTest.java +++ b/src/androidTest/java/de/dennisguse/opentracks/stats/TrackStatisticsUpdaterTest.java @@ -9,6 +9,8 @@ import org.junit.runner.RunWith; import java.time.Duration; import java.time.Instant; +import de.dennisguse.opentracks.content.data.Distance; +import de.dennisguse.opentracks.content.data.Speed; import de.dennisguse.opentracks.content.data.TestDataUtil; import de.dennisguse.opentracks.content.data.Track; import de.dennisguse.opentracks.content.data.TrackPoint; @@ -18,7 +20,7 @@ import static org.junit.Assert.assertEquals; @RunWith(AndroidJUnit4.class) public class TrackStatisticsUpdaterTest { - private static final int GPS_DISTANCE = 50; + private static final Distance GPS_DISTANCE = Distance.of(50); @Test public void addTrackPoint_TestingTrack() { @@ -31,7 +33,7 @@ public class TrackStatisticsUpdaterTest { // then TrackStatistics statistics = subject.getTrackStatistics(); - assertEquals(85.35, statistics.getTotalDistance(), 0.01); + assertEquals(85.35, statistics.getTotalDistance().toM(), 0.01); assertEquals(Duration.ofMillis(13999), statistics.getTotalTime()); assertEquals(Duration.ofSeconds(6), statistics.getMovingTime()); @@ -40,9 +42,9 @@ public class TrackStatisticsUpdaterTest { assertEquals(27, statistics.getTotalAltitudeGain(), 0.01); assertEquals(27.0, statistics.getTotalAltitudeLoss(), 0.01); - assertEquals(14.226, statistics.getMaxSpeed(), 0.01); - assertEquals(14.226, statistics.getAverageMovingSpeed(), 0.01); - assertEquals(6.566, statistics.getAverageSpeed(), 0.01); + assertEquals(14.226, statistics.getMaxSpeed().toMPS(), 0.01); + assertEquals(14.226, statistics.getAverageMovingSpeed().toMPS(), 0.01); + assertEquals(6.566, statistics.getAverageSpeed().toMPS(), 0.01); } @Test @@ -60,7 +62,7 @@ public class TrackStatisticsUpdaterTest { subject.addTrackPoint(tp3, GPS_DISTANCE); // then - assertEquals(0, subject.getTrackStatistics().getTotalDistance(), 0.01); + assertEquals(0, subject.getTrackStatistics().getTotalDistance().toM(), 0.01); } @Test @@ -71,7 +73,7 @@ public class TrackStatisticsUpdaterTest { 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); + tp3.setSpeed(Speed.of(5f)); // when subject.addTrackPoint(tp1, GPS_DISTANCE); @@ -79,7 +81,7 @@ public class TrackStatisticsUpdaterTest { subject.addTrackPoint(tp3, GPS_DISTANCE); // then - assertEquals(1.10, subject.getTrackStatistics().getTotalDistance(), 0.01); + assertEquals(1.10, subject.getTrackStatistics().getTotalDistance().toM(), 0.01); } @Test @@ -89,14 +91,14 @@ public class TrackStatisticsUpdaterTest { 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); + tp2.setSpeed(Speed.of(5f)); TrackPoint tp3 = new TrackPoint(0.001, 0, 5.0, Instant.ofEpochMilli(3000)); - tp2.setSpeed(5f); + tp2.setSpeed(Speed.of(5f)); TrackPoint tp4 = new TrackPoint(0.001, 0, 5.0, Instant.ofEpochMilli(4000)); - tp2.setSpeed(5f); - tp4.setSensorDistance(5f); + tp2.setSpeed(Speed.of(5f)); + tp4.setSensorDistance(Distance.of(5f)); TrackPoint tp5 = new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, Instant.ofEpochMilli(5000)); - tp5.setSensorDistance(10f); + tp5.setSensorDistance(Distance.of(10f)); // when subject.addTrackPoint(tp1, GPS_DISTANCE); @@ -104,14 +106,14 @@ public class TrackStatisticsUpdaterTest { subject.addTrackPoint(tp3, GPS_DISTANCE); // then - assertEquals(110.57, subject.getTrackStatistics().getTotalDistance(), 0.01); + assertEquals(110.57, subject.getTrackStatistics().getTotalDistance().toM(), 0.01); // when subject.addTrackPoint(tp4, GPS_DISTANCE); subject.addTrackPoint(tp5, GPS_DISTANCE); // then - assertEquals(125.57, subject.getTrackStatistics().getTotalDistance(), 0.01); + assertEquals(125.57, subject.getTrackStatistics().getTotalDistance().toM(), 0.01); } @Test @@ -123,9 +125,9 @@ public class TrackStatisticsUpdaterTest { 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); + tp4.setSensorDistance(Distance.of(5f)); TrackPoint tp5 = new TrackPoint(TrackPoint.Type.SEGMENT_END_MANUAL, Instant.ofEpochMilli(5000)); - tp5.setSensorDistance(10f); + tp5.setSensorDistance(Distance.of(10f)); // when subject.addTrackPoint(tp1, GPS_DISTANCE); @@ -133,14 +135,14 @@ public class TrackStatisticsUpdaterTest { subject.addTrackPoint(tp3, GPS_DISTANCE); // then - assertEquals(0, subject.getTrackStatistics().getTotalDistance(), 0.01); + assertEquals(0, subject.getTrackStatistics().getTotalDistance().toM(), 0.01); // when subject.addTrackPoint(tp4, GPS_DISTANCE); subject.addTrackPoint(tp5, GPS_DISTANCE); // then - assertEquals(15, subject.getTrackStatistics().getTotalDistance(), 0.01); + assertEquals(15, subject.getTrackStatistics().getTotalDistance().toM(), 0.01); } @Ignore("TODO: create a concept ont to compute speed from GPS and sensor") diff --git a/src/androidTest/java/de/dennisguse/opentracks/util/AnnouncementUtilsTest.java b/src/androidTest/java/de/dennisguse/opentracks/util/AnnouncementUtilsTest.java index a979ee6e3..fb1469796 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/util/AnnouncementUtilsTest.java +++ b/src/androidTest/java/de/dennisguse/opentracks/util/AnnouncementUtilsTest.java @@ -11,6 +11,8 @@ import org.junit.runner.RunWith; import java.time.Duration; import java.util.List; +import de.dennisguse.opentracks.content.data.Distance; +import de.dennisguse.opentracks.content.data.Speed; import de.dennisguse.opentracks.content.data.TestDataUtil; import de.dennisguse.opentracks.content.data.Track; import de.dennisguse.opentracks.content.data.TrackPoint; @@ -27,10 +29,10 @@ public class AnnouncementUtilsTest { @Test public void getAnnouncement_metric() { TrackStatistics stats = new TrackStatistics(); - stats.setTotalDistance(20000); + stats.setTotalDistance(Distance.of(20000)); stats.setTotalTime(Duration.ofHours(2).plusMinutes(5).plusSeconds(10)); stats.setMovingTime(Duration.ofHours(1).plusMinutes(5).plusSeconds(10)); - stats.setMaxSpeed(100); + stats.setMaxSpeed(Speed.of(100)); stats.setTotalAltitudeGain(6000f); // when @@ -43,10 +45,10 @@ public class AnnouncementUtilsTest { @Test public void getAnnouncement_withInterval_metric() { TrackStatistics stats = new TrackStatistics(); - stats.setTotalDistance(20000); + stats.setTotalDistance(Distance.of(20000)); stats.setTotalTime(Duration.ofHours(2).plusMinutes(5).plusSeconds(10)); stats.setMovingTime(Duration.ofHours(1).plusMinutes(5).plusSeconds(10)); - stats.setMaxSpeed(100); + stats.setMaxSpeed(Speed.of(100)); stats.setTotalAltitudeGain(6000f); List trackPoints = TestDataUtil.createTrack(new Track.Id(System.currentTimeMillis()), 10).second; diff --git a/src/androidTest/java/de/dennisguse/opentracks/util/StringUtilsTest.java b/src/androidTest/java/de/dennisguse/opentracks/util/StringUtilsTest.java index 983d166b8..4ae12974c 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/util/StringUtilsTest.java +++ b/src/androidTest/java/de/dennisguse/opentracks/util/StringUtilsTest.java @@ -28,6 +28,9 @@ import java.time.Duration; import java.util.GregorianCalendar; import java.util.TimeZone; +import de.dennisguse.opentracks.content.data.Distance; +import de.dennisguse.opentracks.content.data.Speed; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; @@ -70,19 +73,18 @@ public class StringUtilsTest { } /** - * Tests {@link StringUtils#formatDistance(android.content.Context, double, - * boolean)}. + * Tests {@link StringUtils#formatDistance(android.content.Context, Distance, boolean)}. */ @Test public void testFormatDistance() { // A large number in metric - assertEquals("5.00 km", StringUtils.formatDistance(context, 5000, true)); + assertEquals("5.00 km", StringUtils.formatDistance(context, Distance.of(5000), true)); // A large number in imperial - assertEquals("3.11 mi", StringUtils.formatDistance(context, 5000, false)); + assertEquals("3.11 mi", StringUtils.formatDistance(context, Distance.of(5000), false)); // A small number in metric - assertEquals("100.00 m", StringUtils.formatDistance(context, 100, true)); + assertEquals("100.00 m", StringUtils.formatDistance(context, Distance.of(100), true)); // A small number in imperial - assertEquals("328.08 ft", StringUtils.formatDistance(context, 100, false)); + assertEquals("328.08 ft", StringUtils.formatDistance(context, Distance.of(100), false)); } /** @@ -164,22 +166,22 @@ public class StringUtilsTest { @Test public void testGetSpeedParts() { - assertEquals("4:59", StringUtils.getSpeedParts(context, 3.34, true, false).first); - assertEquals("5:00", StringUtils.getSpeedParts(context, 3.33, true, false).first); + assertEquals("4:59", StringUtils.getSpeedParts(context, Speed.of(3.34), true, false).first); + assertEquals("5:00", StringUtils.getSpeedParts(context, Speed.of(3.33), true, false).first); - assertEquals("11.9", StringUtils.getSpeedParts(context, 3.31, true, true).first); - assertEquals("7.5", StringUtils.getSpeedParts(context, 3.34, false, true).first); + assertEquals("11.9", StringUtils.getSpeedParts(context, Speed.of(3.31), true, true).first); + assertEquals("7.5", StringUtils.getSpeedParts(context, Speed.of(3.34), false, true).first); - assertEquals("min/km", StringUtils.getSpeedParts(context, 0, true, false).second); - assertEquals("min/mi", StringUtils.getSpeedParts(context, 0, false, false).second); + assertEquals("min/km", StringUtils.getSpeedParts(context, Speed.zero(), true, false).second); + assertEquals("min/mi", StringUtils.getSpeedParts(context, Speed.zero(), false, false).second); } @Test public void testFormatSpeed() { - assertEquals("4:59 min/km", StringUtils.formatSpeed(context, 3.34, true, false)); - assertEquals("8:02 min/mi", StringUtils.formatSpeed(context, 3.34, false, false)); + assertEquals("4:59 min/km", StringUtils.formatSpeed(context, Speed.of(3.34), true, false)); + assertEquals("8:02 min/mi", StringUtils.formatSpeed(context, Speed.of(3.34), false, false)); - assertEquals("12.02 km/h", StringUtils.formatSpeed(context, 3.34, true, true)); - assertEquals("7.47 mph", StringUtils.formatSpeed(context, 3.34, false, true)); + assertEquals("12.02 km/h", StringUtils.formatSpeed(context, Speed.of(3.34), true, true)); + assertEquals("7.47 mph", StringUtils.formatSpeed(context, Speed.of(3.34), false, true)); } } diff --git a/src/androidTest/java/de/dennisguse/opentracks/viewmodels/AggregatedStatisticsTest.java b/src/androidTest/java/de/dennisguse/opentracks/viewmodels/AggregatedStatisticsTest.java index 2d763f444..4a22a8760 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/viewmodels/AggregatedStatisticsTest.java +++ b/src/androidTest/java/de/dennisguse/opentracks/viewmodels/AggregatedStatisticsTest.java @@ -13,6 +13,8 @@ import java.time.Instant; import java.util.List; import de.dennisguse.opentracks.R; +import de.dennisguse.opentracks.content.data.Distance; +import de.dennisguse.opentracks.content.data.Speed; import de.dennisguse.opentracks.content.data.Track; import de.dennisguse.opentracks.stats.TrackStatistics; import de.dennisguse.opentracks.util.TrackIconUtils; @@ -25,15 +27,15 @@ public class AggregatedStatisticsTest { private final Context context = ApplicationProvider.getApplicationContext(); - private static Track createTrack(Context context, long totalDistance, long totalTime, String category) { + private static Track createTrack(Context context, Distance totalDistance, Duration totalTime, String category) { TrackStatistics statistics = new TrackStatistics(); statistics.setStartTime(Instant.ofEpochMilli(1000L)); // Resulting start time - statistics.setStopTime(Instant.ofEpochMilli(1000L + totalTime)); - statistics.setTotalTime(Duration.ofMillis(totalTime)); - statistics.setMovingTime(Duration.ofMillis(totalTime)); + statistics.setStopTime(statistics.getStartTime().plus(totalTime)); + statistics.setTotalTime(totalTime); + statistics.setMovingTime(totalTime); statistics.setTotalDistance(totalDistance); statistics.setTotalAltitudeGain(50.0f); - statistics.setMaxSpeed(50.0); // Resulting max speed + statistics.setMaxSpeed(Speed.of(50.0)); // Resulting max speed statistics.setMaxAltitude(1250.0); statistics.setMinAltitude(1200.0); // Resulting min altitude @@ -48,8 +50,8 @@ public class AggregatedStatisticsTest { public void testAggregate() { // given // 10km in 40 minutes. - long totalDistance = 10000; - long totalTime = 2400000; + Distance totalDistance = Distance.of(10000); + Duration totalTime = Duration.ofMillis(2400000); String biking = context.getString(R.string.activity_type_biking); Track track = createTrack(context, totalDistance, totalTime, biking); @@ -62,16 +64,16 @@ public class AggregatedStatisticsTest { assertEquals(1, aggregatedStatistics.get(biking).getCountTracks()); TrackStatistics statistics2 = aggregatedStatistics.get(biking).getTrackStatistics(); - assertEquals(totalDistance, statistics2.getTotalDistance(), 0); - assertEquals(Duration.ofMillis(totalTime), statistics2.getMovingTime()); + assertEquals(totalDistance, statistics2.getTotalDistance()); + assertEquals(totalTime, statistics2.getMovingTime()); } @Test public void testAggregate_mountainBiking() { // given // 10km in 40 minutes. - long totalDistance = 10000; - long totalTime = 2400000; + Distance totalDistance = Distance.of(10000); + Duration totalTime = Duration.ofMillis(2400000); String mountainBiking = context.getString(R.string.activity_type_mountain_biking); Track track = createTrack(context, totalDistance, totalTime, mountainBiking); @@ -86,8 +88,8 @@ public class AggregatedStatisticsTest { public void testAggregate_trailRunning() { // given // 10km in 40 minutes. - long totalDistance = 10000; - long totalTime = 2400000; + Distance totalDistance = Distance.of(10000); + Duration totalTime = Duration.ofMillis(2400000); String trailRunning = context.getString(R.string.activity_type_trail_running); Track track = createTrack(context, totalDistance, totalTime, trailRunning); @@ -102,8 +104,8 @@ public class AggregatedStatisticsTest { public void testAggregate_twoBikingTracks() { // given // 10km in 40 minutes. - long totalDistance = 10000; - long totalTime = 2400000; + Distance totalDistance = Distance.of(10000); + Duration totalTime = Duration.ofMillis(2400000); String biking = context.getString(R.string.activity_type_biking); List tracks = List.of(createTrack(context, totalDistance, totalTime, biking), createTrack(context, totalDistance, totalTime, biking)); @@ -116,20 +118,20 @@ public class AggregatedStatisticsTest { assertEquals(2, aggregatedStatistics.get(biking).getCountTracks()); TrackStatistics statistics2 = aggregatedStatistics.get(biking).getTrackStatistics(); - assertEquals(totalDistance * 2, statistics2.getTotalDistance(), 0); - assertEquals(Duration.ofMillis(totalTime * 2), statistics2.getMovingTime()); + assertEquals(totalDistance.multipliedBy(2), statistics2.getTotalDistance()); + assertEquals(totalTime.multipliedBy(2), statistics2.getMovingTime()); } @Test public void testAggregate_threeDifferentTracks() { // given // 10km in 40 minutes. - long totalDistance = 10000; + Distance totalDistance = Distance.of(10000); String biking = context.getString(R.string.activity_type_biking); String running = context.getString(R.string.activity_type_running); String walking = context.getString(R.string.activity_type_walking); - long totalTime = 2400000; + Duration totalTime = Duration.ofMillis(2400000); List tracks = List.of( createTrack(context, totalDistance, totalTime, biking), createTrack(context, totalDistance, totalTime, running), @@ -150,20 +152,20 @@ public class AggregatedStatisticsTest { { TrackStatistics statistics2 = aggregatedStatistics.get(biking).getTrackStatistics(); - assertEquals(totalDistance, statistics2.getTotalDistance(), 0); - assertEquals(Duration.ofMillis(totalTime), statistics2.getMovingTime()); + assertEquals(totalDistance, statistics2.getTotalDistance()); + assertEquals(totalTime, statistics2.getMovingTime()); } { TrackStatistics statistics2 = aggregatedStatistics.get(running).getTrackStatistics(); - assertEquals(totalDistance, statistics2.getTotalDistance(), 0); - assertEquals(Duration.ofMillis(totalTime), statistics2.getMovingTime()); + assertEquals(totalDistance, statistics2.getTotalDistance()); + assertEquals(totalTime, statistics2.getMovingTime()); } { TrackStatistics statistics2 = aggregatedStatistics.get(walking).getTrackStatistics(); - assertEquals(totalDistance, statistics2.getTotalDistance(), 0); - assertEquals(Duration.ofMillis(totalTime), statistics2.getMovingTime()); + assertEquals(totalDistance, statistics2.getTotalDistance()); + assertEquals(totalTime, statistics2.getMovingTime()); } } @@ -171,8 +173,8 @@ public class AggregatedStatisticsTest { public void testAggregate_severalTracksWithSeveralActivities() { // given // 10km in 40 minutes. - long totalDistance = 10000; - long totalTime = 2400000; + Distance totalDistance = Distance.of(10000); + Duration totalTime = Duration.ofMillis(2400000); String biking = context.getString(R.string.activity_type_biking); String running = context.getString(R.string.activity_type_running); String walking = context.getString(R.string.activity_type_walking); @@ -213,29 +215,29 @@ public class AggregatedStatisticsTest { // Biking. { TrackStatistics statistics2 = aggregatedStatistics.get(biking).getTrackStatistics(); - assertEquals(totalDistance * 5, statistics2.getTotalDistance(), 0); - assertEquals(Duration.ofMillis(totalTime * 5), statistics2.getMovingTime()); + assertEquals(totalDistance.multipliedBy(5), statistics2.getTotalDistance()); + assertEquals(totalTime.multipliedBy(5), statistics2.getMovingTime()); } // Running. { TrackStatistics statistics2 = aggregatedStatistics.get(running).getTrackStatistics(); - assertEquals(totalDistance * 2, statistics2.getTotalDistance(), 0); - assertEquals(Duration.ofMillis(totalTime * 2), statistics2.getMovingTime()); + assertEquals(totalDistance.multipliedBy(2), statistics2.getTotalDistance()); + assertEquals(totalTime.multipliedBy(2), statistics2.getMovingTime()); } // Walking. { TrackStatistics statistics2 = aggregatedStatistics.get(walking).getTrackStatistics(); - assertEquals(totalDistance * 2, statistics2.getTotalDistance(), 0); - assertEquals(Duration.ofMillis(totalTime * 2), statistics2.getMovingTime()); + assertEquals(totalDistance.multipliedBy(2), statistics2.getTotalDistance()); + assertEquals(totalTime.multipliedBy(2), statistics2.getMovingTime()); } // Driving. { TrackStatistics statistics2 = aggregatedStatistics.get(driving).getTrackStatistics(); - assertEquals(totalDistance, statistics2.getTotalDistance(), 0); - assertEquals(Duration.ofMillis(totalTime), statistics2.getMovingTime()); + assertEquals(totalDistance, statistics2.getTotalDistance()); + assertEquals(totalTime, statistics2.getMovingTime()); } // Check order diff --git a/src/androidTest/java/de/dennisguse/opentracks/viewmodels/IntervalStatisticsTest.java b/src/androidTest/java/de/dennisguse/opentracks/viewmodels/IntervalStatisticsTest.java index 71e5a3b58..38e208cc5 100644 --- a/src/androidTest/java/de/dennisguse/opentracks/viewmodels/IntervalStatisticsTest.java +++ b/src/androidTest/java/de/dennisguse/opentracks/viewmodels/IntervalStatisticsTest.java @@ -6,6 +6,7 @@ import org.junit.runners.JUnit4; import java.util.List; +import de.dennisguse.opentracks.content.data.Distance; import de.dennisguse.opentracks.content.data.TestDataUtil; import de.dennisguse.opentracks.content.data.Track; import de.dennisguse.opentracks.content.data.TrackPoint; @@ -18,121 +19,121 @@ import static org.junit.Assert.assertEquals; @RunWith(JUnit4.class) public class IntervalStatisticsTest { - private static final String TAG = IntervalStatisticsTest.class.getSimpleName(); + private static final String TAG = IntervalStatisticsTest.class.getSimpleName(); - private List buildTrackPoints(int numberOfTrackPoints) { - return TestDataUtil.createTrack(new Track.Id(System.currentTimeMillis()), numberOfTrackPoints).second; - } + private List buildTrackPoints(int numberOfTrackPoints) { + return TestDataUtil.createTrack(new Track.Id(System.currentTimeMillis()), numberOfTrackPoints).second; + } - private TrackStatistics buildTrackStatistics(List trackPoints) { + private TrackStatistics buildTrackStatistics(List trackPoints) { TrackStatisticsUpdater trackStatisticsUpdater = new TrackStatisticsUpdater(); for (TrackPoint tp : trackPoints) { - trackStatisticsUpdater.addTrackPoint(tp, 0); + trackStatisticsUpdater.addTrackPoint(tp, Distance.of(0)); } return trackStatisticsUpdater.getTrackStatistics(); } - /** - * Tests that build method compute the distance correctly comparing the result with TrackStatisticsUpdater result. - */ - @Test - public void testBuild_1() { - // With 50 points and interval distance of 1000m. + /** + * Tests that build method compute the distance correctly comparing the result with TrackStatisticsUpdater result. + */ + @Test + public void testBuild_1() { + // With 50 points and interval distance of 1000m. - // given - List trackPoints = buildTrackPoints(50); - TrackStatistics trackStatistics = buildTrackStatistics(trackPoints); - float distanceInterval = 1000f; + // given + List trackPoints = buildTrackPoints(50); + TrackStatistics trackStatistics = buildTrackStatistics(trackPoints); + float distanceInterval = 1000f; - // when and then - whenAndThen(trackPoints, trackStatistics, distanceInterval); - } + // when and then + whenAndThen(trackPoints, trackStatistics, distanceInterval); + } - /** - * Tests that build method compute the distance correctly comparing the result with TrackStatisticsUpdater result. - */ - @Test - public void testBuild_2() { - // With 200 points and interval distance of 1000m. + /** + * Tests that build method compute the distance correctly comparing the result with TrackStatisticsUpdater result. + */ + @Test + public void testBuild_2() { + // With 200 points and interval distance of 1000m. - // given - List trackPoints = buildTrackPoints(200); - TrackStatistics trackStatistics = buildTrackStatistics(trackPoints); - float distanceInterval = 1000f; + // given + List trackPoints = buildTrackPoints(200); + TrackStatistics trackStatistics = buildTrackStatistics(trackPoints); + float distanceInterval = 1000f; - // when and then - whenAndThen(trackPoints, trackStatistics, distanceInterval); - } + // when and then + whenAndThen(trackPoints, trackStatistics, distanceInterval); + } - /** - * Tests that build method compute the distance correctly comparing the result with TrackStatisticsUpdater result. - */ - @Test - public void testBuild_3() { - // With 200 points and interval distance of 3000m. + /** + * Tests that build method compute the distance correctly comparing the result with TrackStatisticsUpdater result. + */ + @Test + public void testBuild_3() { + // With 200 points and interval distance of 3000m. - // given - List trackPoints = buildTrackPoints(200); - TrackStatistics trackStatistics = buildTrackStatistics(trackPoints); - float distanceInterval = 3000f; + // given + List trackPoints = buildTrackPoints(200); + TrackStatistics trackStatistics = buildTrackStatistics(trackPoints); + float distanceInterval = 3000f; - // when and then - whenAndThen(trackPoints, trackStatistics, distanceInterval); - } + // when and then + whenAndThen(trackPoints, trackStatistics, distanceInterval); + } - /** - * Tests that build method compute the distance correctly comparing the result with TrackStatisticsUpdater result. - */ - @Test - public void testBuild_4() { - // With 1000 points and interval distance of 3000m. + /** + * Tests that build method compute the distance correctly comparing the result with TrackStatisticsUpdater result. + */ + @Test + public void testBuild_4() { + // With 1000 points and interval distance of 3000m. - // given - List trackPoints = buildTrackPoints(1000); - TrackStatistics trackStatistics = buildTrackStatistics(trackPoints); - float distanceInterval = 3000f; + // given + List trackPoints = buildTrackPoints(1000); + TrackStatistics trackStatistics = buildTrackStatistics(trackPoints); + float distanceInterval = 3000f; - // when and then - whenAndThen(trackPoints, trackStatistics, distanceInterval); - } + // when and then + whenAndThen(trackPoints, trackStatistics, distanceInterval); + } - /** - * Tests that build method compute the distance correctly comparing the result with TrackStatisticsUpdater result. - */ - @Test - public void testBuild_5() { - // With 10000 points and interval distance of 1000m. + /** + * Tests that build method compute the distance correctly comparing the result with TrackStatisticsUpdater result. + */ + @Test + public void testBuild_5() { + // With 10000 points and interval distance of 1000m. - // given - List trackPoints = buildTrackPoints(10000); - TrackStatistics trackStatistics = buildTrackStatistics(trackPoints); - float distanceInterval = 1000f; + // given + List trackPoints = buildTrackPoints(10000); + TrackStatistics trackStatistics = buildTrackStatistics(trackPoints); + float distanceInterval = 1000f; - // when and then - whenAndThen(trackPoints, trackStatistics, distanceInterval); - } + // when and then + whenAndThen(trackPoints, trackStatistics, distanceInterval); + } - private void whenAndThen(List trackPoints, TrackStatistics trackStatistics, float distanceInterval) { - IntervalStatistics intervalStatistics = new IntervalStatistics(trackPoints, distanceInterval); - List intervalList = intervalStatistics.getIntervalList(); - double totalDistance = 0d; - float totalTime = 0L; - float totalGain = 0f; - for (IntervalStatistics.Interval i : intervalList) { - totalDistance += i.getDistance_m(); - totalTime += i.getDistance_m() / i.getSpeed_ms(); - totalGain += i.getGain_m(); - } + private void whenAndThen(List trackPoints, TrackStatistics trackStatistics, float distanceInterval) { + IntervalStatistics intervalStatistics = new IntervalStatistics(trackPoints, distanceInterval); + List intervalList = intervalStatistics.getIntervalList(); + double totalDistance = 0d; + float totalTime = 0L; + float totalGain = 0f; + for (IntervalStatistics.Interval i : intervalList) { + totalDistance += i.getDistance_m(); + totalTime += i.getDistance_m() / i.getSpeed_ms(); + totalGain += i.getGain_m(); + } - // then - assertEquals(trackStatistics.getTotalDistance(), totalDistance, 0.01); - assertEquals(trackStatistics.getTotalTime().toMillis(), totalTime * UnitConversions.S_TO_MS, 1); - assertEquals(intervalList.size(), (int) Math.ceil(trackStatistics.getTotalDistance() / distanceInterval)); - assertEquals(totalGain, trackPoints.size() * TestDataUtil.ALTITUDE_GAIN, 0.1); - for (int i = 0; i < intervalList.size() - 1; i++) { - assertEquals(intervalList.get(i).getDistance_m(), distanceInterval, 0.001); - totalDistance -= intervalList.get(i).getDistance_m(); - } - assertEquals(intervalList.get(intervalList.size() - 1).getDistance_m(), totalDistance, 0.01); - } + // then + assertEquals(trackStatistics.getTotalDistance().toM(), totalDistance, 0.01); + assertEquals(trackStatistics.getTotalTime().toMillis(), totalTime * UnitConversions.S_TO_MS, 1); + assertEquals(intervalList.size(), (int) Math.ceil(trackStatistics.getTotalDistance().toM() / distanceInterval)); + assertEquals(totalGain, trackPoints.size() * TestDataUtil.ALTITUDE_GAIN, 0.1); + for (int i = 0; i < intervalList.size() - 1; i++) { + assertEquals(intervalList.get(i).getDistance_m(), distanceInterval, 0.001); + totalDistance -= intervalList.get(i).getDistance_m(); + } + assertEquals(intervalList.get(intervalList.size() - 1).getDistance_m(), totalDistance, 0.01); + } } \ No newline at end of file diff --git a/src/main/java/de/dennisguse/opentracks/TrackListActivity.java b/src/main/java/de/dennisguse/opentracks/TrackListActivity.java index 20b731974..538c1ee52 100644 --- a/src/main/java/de/dennisguse/opentracks/TrackListActivity.java +++ b/src/main/java/de/dennisguse/opentracks/TrackListActivity.java @@ -48,6 +48,7 @@ import androidx.loader.content.Loader; import java.time.Duration; +import de.dennisguse.opentracks.content.data.Distance; import de.dennisguse.opentracks.content.data.Track; import de.dennisguse.opentracks.content.data.TracksColumns; import de.dennisguse.opentracks.databinding.TrackListBinding; @@ -215,7 +216,7 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe int iconId = TrackIconUtils.getIconDrawable(icon); String name = cursor.getString(nameIndex); String totalTime = StringUtils.formatElapsedTime(Duration.ofMillis(cursor.getLong(totalTimeIndex))); - String totalDistance = StringUtils.formatDistance(TrackListActivity.this, cursor.getDouble(totalDistanceIndex), metricUnits); + String totalDistance = StringUtils.formatDistance(TrackListActivity.this, Distance.of(cursor.getDouble(totalDistanceIndex)), metricUnits); int markerCount = cursor.getInt(markerCountIndex); long startTime = cursor.getLong(startTimeIndex); String category = icon != null && !icon.equals("") ? null : cursor.getString(categoryIndex); diff --git a/src/main/java/de/dennisguse/opentracks/adapters/IntervalStatisticsAdapter.java b/src/main/java/de/dennisguse/opentracks/adapters/IntervalStatisticsAdapter.java index e782a062b..df598417b 100644 --- a/src/main/java/de/dennisguse/opentracks/adapters/IntervalStatisticsAdapter.java +++ b/src/main/java/de/dennisguse/opentracks/adapters/IntervalStatisticsAdapter.java @@ -12,6 +12,8 @@ import androidx.recyclerview.widget.RecyclerView; import java.util.List; import de.dennisguse.opentracks.R; +import de.dennisguse.opentracks.content.data.Distance; +import de.dennisguse.opentracks.content.data.Speed; import de.dennisguse.opentracks.util.StringUtils; import de.dennisguse.opentracks.viewmodels.IntervalStatistics; @@ -52,12 +54,12 @@ public class IntervalStatisticsAdapter extends RecyclerView.Adapter paceInMetrics = StringUtils.getSpeedParts(context, speed, true, false); Pair paceInImperial = StringUtils.getSpeedParts(context, speed, false, false); diff --git a/src/main/java/de/dennisguse/opentracks/content/data/Distance.java b/src/main/java/de/dennisguse/opentracks/content/data/Distance.java new file mode 100644 index 000000000..c1453bc10 --- /dev/null +++ b/src/main/java/de/dennisguse/opentracks/content/data/Distance.java @@ -0,0 +1,116 @@ +package de.dennisguse.opentracks.content.data; + +import androidx.annotation.NonNull; + +import java.util.Objects; + +import de.dennisguse.opentracks.util.UnitConversions; + +public class Distance { + + public static Distance of(double distance_m) { + return new Distance(distance_m); + } + + public static Distance of(String distance_m) { + return of(Float.parseFloat(distance_m)); + } + + public static Distance ofMile(double distance_mile) { + return of(distance_mile * UnitConversions.MI_TO_M); + } + + public static Distance invalid() { + return of(Double.NaN); + } + + private final double distance_m; + + private Distance(double distance_m) { + this.distance_m = distance_m; + } + + public Distance plus(@NonNull Distance distance) { + return new Distance(distance_m + distance.distance_m); + } + + public Distance multipliedBy(double factor) { + return new Distance(factor * distance_m); + } + + public boolean isZero() { + return distance_m == 0; + } + + public boolean isInvalid() { + return Double.isNaN(distance_m) || Double.isInfinite(distance_m); + } + + public boolean lessThan(@NonNull Distance distance) { + return !greaterThan(distance); + } + + public boolean greaterThan(@NonNull Distance distance) { + return distance_m > distance.distance_m; + } + + public boolean greaterOrEqualThan(@NonNull Distance distance) { + return distance_m >= distance.distance_m; + } + + public double toM() { + return distance_m; + } + + public double toKM() { + return distance_m * UnitConversions.M_TO_KM; + } + + public double toFT() { + return distance_m * UnitConversions.M_TO_FT; + } + + public double toMI() { + return toKM() * UnitConversions.KM_TO_MI; + } + + public double to(boolean metricUnit) { + return to(metricUnit ? Unit.KM : Unit.MILES); + } + + public double to(Unit unit) { + switch (unit) { + case KM: + return toKM(); + case MILES: + return toMI(); + default: + throw new RuntimeException("Not implemented"); + } + } + + public enum Unit { + KM, + MILES + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Distance distance = (Distance) o; + return Double.compare(distance.distance_m, distance_m) == 0; + } + + @Override + public int hashCode() { + return Objects.hash(distance_m); + } + + @Override + public String toString() { + return "Distance{" + + "distance_m=" + distance_m + + '}'; + } +} diff --git a/src/main/java/de/dennisguse/opentracks/content/data/Marker.java b/src/main/java/de/dennisguse/opentracks/content/data/Marker.java index d039b588c..e127278dc 100644 --- a/src/main/java/de/dennisguse/opentracks/content/data/Marker.java +++ b/src/main/java/de/dennisguse/opentracks/content/data/Marker.java @@ -28,12 +28,15 @@ import java.time.Duration; import java.time.Instant; import java.util.Objects; +import de.dennisguse.opentracks.stats.TrackStatistics; + /** * NOTE: A marker is indirectly (via it's location) assigned to one {@link TrackPoint} with trackPoint.hasLocation() == true. * * @author Leif Hendrik Wilden * @author Rodrigo Damazio */ +//TODO All data should be final; no default values. public final class Marker { private Id id; @@ -51,7 +54,7 @@ public final class Marker { private Float bearing; //TODO It is the distance from the track starting point; rename to something more meaningful - private double length = 0.0; + private Distance length; private Duration duration; @Deprecated //TODO Make an URI instead of String @@ -62,6 +65,7 @@ public final class Marker { this.time = time; } + @Deprecated //TODO Marker cannot be created without length AND duration! public Marker(@Nullable Track.Id trackId, @NonNull TrackPoint trackPoint) { this.trackId = trackId; @@ -75,16 +79,19 @@ public final class Marker { if (trackPoint.hasAccuracy()) this.accuracy = trackPoint.getAccuracy(); if (trackPoint.hasAltitude()) this.altitude_m = trackPoint.getAltitude(); if (trackPoint.hasBearing()) this.bearing = trackPoint.getBearing(); + + this.length = Distance.of(0); //TODO Not cool! + this.duration = Duration.ofMillis(0); //TODO Not cool! } - public Marker(String name, String description, String category, String icon, @NonNull Track.Id trackId, double length, @NonNull Duration duration, @NonNull TrackPoint trackPoint, String photoUrl) { + public Marker(String name, String description, String category, String icon, @NonNull Track.Id trackId, @NonNull TrackStatistics statistics, @NonNull TrackPoint trackPoint, String photoUrl) { this(trackId, trackPoint); this.name = name; this.description = description; this.category = category; this.icon = icon; - this.length = length; - this.duration = duration; + this.length = statistics.getTotalDistance(); + this.duration = statistics.getTotalTime(); this.photoUrl = photoUrl; } @@ -218,11 +225,11 @@ public final class Marker { this.bearing = bearing; } - public double getLength() { + public Distance getLength() { return length; } - public void setLength(double length) { + public void setLength(Distance length) { this.length = length; } diff --git a/src/main/java/de/dennisguse/opentracks/content/data/Speed.java b/src/main/java/de/dennisguse/opentracks/content/data/Speed.java new file mode 100644 index 000000000..629c9d2d1 --- /dev/null +++ b/src/main/java/de/dennisguse/opentracks/content/data/Speed.java @@ -0,0 +1,145 @@ +package de.dennisguse.opentracks.content.data; + +import java.time.Duration; +import java.util.Objects; + +import de.dennisguse.opentracks.util.UnitConversions; + +public class Speed { + + public static Speed of(Distance distance, Duration duration) { + if (duration.isZero()) { + return zero(); + } + + return new Speed(distance.toM() / (duration.toMillis() * UnitConversions.MS_TO_S)); + } + + public static Speed of(double speed_mps) { + return new Speed(speed_mps); + } + + public static Speed of(String speed_mps) { + return of(Float.parseFloat(speed_mps)); + } + + public static Speed invalid() { + return of(Double.NaN); + } + + public static Speed zero() { + return of(0.0); + } + + public static Speed max(Speed speed1, Speed speed2) { + if (speed1.greaterThan(speed2)) { + return speed1; + } + + return speed2; + } + + public static Speed absDiff(Speed speed1, Speed speed2) { + //TODO Why Math.abs? Seems to be a leftover. + return Speed.of(Math.abs(speed1.speed_mps - speed2.speed_mps)); + } + + + // Anything faster than that (in meters per second) will be considered moving. + private static final double MAX_NO_MOVEMENT_SPEED = 0.224; + + private final double speed_mps; + + private Speed(double speed_mps) { + this.speed_mps = speed_mps; + } + + public Speed mul(double factor) { + return new Speed(factor * speed_mps); + } + + public boolean isZero() { + return speed_mps == 0; + } + + public boolean isInvalid() { + return Double.isNaN(speed_mps) || Double.isInfinite(speed_mps); + } + + public boolean isMoving() { + return !isInvalid() && speed_mps >= MAX_NO_MOVEMENT_SPEED; + } + + public boolean lessThan(Speed speed) { + return !greaterThan(speed); + } + + public boolean greaterThan(Speed speed) { + return speed_mps > speed.speed_mps; + } + + public boolean greaterOrEqualThan(Speed speed) { + return speed_mps >= speed.speed_mps; + } + + public double toMPS() { + return speed_mps; + } + + public double toKMH() { + return speed_mps * UnitConversions.MPS_TO_KMH; + } + + public double toMPH() { + return toKMH() * UnitConversions.KM_TO_MI; + } + + public Duration toPace(boolean metricUnit) { + if (isZero()) { + return Duration.ofSeconds(0); + } + + double distance = speed_mps * (metricUnit ? UnitConversions.M_TO_KM : UnitConversions.M_TO_MI); + return Duration.ofSeconds(Math.round(1 / distance)); + } + + public double to(boolean metricUnit) { + return to(metricUnit ? Unit.KMH : Unit.MPH); + } + + public double to(Unit unit) { + switch (unit) { + case KMH: + return toKMH(); + case MPH: + return toMPH(); + default: + throw new RuntimeException("Not implemented"); + } + } + + public enum Unit { + KMH, + MPH, + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Speed speed = (Speed) o; + return Double.compare(speed.speed_mps, speed_mps) == 0; + } + + @Override + public int hashCode() { + return Objects.hash(speed_mps); + } + + @Override + public String toString() { + return "Speed{" + + "speed_mps=" + speed_mps + + '}'; + } +} 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 6a8bb5fbb..66f9f36d5 100644 --- a/src/main/java/de/dennisguse/opentracks/content/data/TrackPoint.java +++ b/src/main/java/de/dennisguse/opentracks/content/data/TrackPoint.java @@ -45,9 +45,6 @@ public class TrackPoint { private static final Duration MAX_LOCATION_AGE = Duration.ofMinutes(1); - // Anything faster than that (in meters per second) will be considered moving. - private static final double MAX_NO_MOVEMENT_SPEED = 0.224; - private TrackPoint.Id id; private Instant time; @@ -55,9 +52,9 @@ public class TrackPoint { private Double longitude; private Float accuracy; private Double altitude_m; - private Float speed_mps; + private Speed speed; private Float bearing; - private Float sensorDistance_m; + private Distance sensorDistance_m; public enum Type { SEGMENT_START_MANUAL(-2), //Start of a segment due to user interaction (start, resume) @@ -106,7 +103,7 @@ public class TrackPoint { this.latitude = location.getLatitude(); this.longitude = location.getLongitude(); this.altitude_m = location.getAltitude(); - this.speed_mps = location.getSpeed(); + this.speed = Speed.of(location.getSpeed()); this.accuracy = location.getAccuracy(); setTime(Instant.now()); @@ -272,19 +269,19 @@ public class TrackPoint { } public boolean hasSpeed() { - return speed_mps != null; + return speed != null; } - public float getSpeed() { - return speed_mps; + public Speed getSpeed() { + return speed; } - public void setSpeed(Float speed) { - this.speed_mps = speed; + public void setSpeed(Speed speed) { + this.speed = speed; } public boolean isMoving() { - return hasSpeed() && getSpeed() >= MAX_NO_MOVEMENT_SPEED; + return hasSpeed() && getSpeed().isMoving(); } public boolean hasBearing() { @@ -311,12 +308,12 @@ public class TrackPoint { this.accuracy = horizontalAccuracy; } - public float distanceToPrevious(@NonNull TrackPoint previous) { + public Distance distanceToPrevious(@NonNull TrackPoint previous) { if (hasSensorDistance()) { return getSensorDistance(); } - return getLocation().distanceTo(previous.getLocation()); + return Distance.of(getLocation().distanceTo(previous.getLocation())); } public boolean fulfillsAccuracy(int poorAccuracy) { @@ -336,11 +333,11 @@ public class TrackPoint { return sensorDistance_m != null; } - public Float getSensorDistance() { + public Distance getSensorDistance() { return sensorDistance_m; } - public void setSensorDistance(Float distance_m) { + public void setSensorDistance(Distance distance_m) { this.sensorDistance_m = distance_m; } 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 611ab97ed..fdbac7b28 100644 --- a/src/main/java/de/dennisguse/opentracks/content/provider/ContentProviderUtils.java +++ b/src/main/java/de/dennisguse/opentracks/content/provider/ContentProviderUtils.java @@ -39,8 +39,10 @@ import java.util.List; import java.util.UUID; import de.dennisguse.opentracks.BuildConfig; +import de.dennisguse.opentracks.content.data.Distance; import de.dennisguse.opentracks.content.data.Marker; import de.dennisguse.opentracks.content.data.MarkerColumns; +import de.dennisguse.opentracks.content.data.Speed; import de.dennisguse.opentracks.content.data.Track; import de.dennisguse.opentracks.content.data.TrackPoint; import de.dennisguse.opentracks.content.data.TrackPointsColumns; @@ -127,7 +129,7 @@ public class ContentProviderUtils { trackStatistics.setStopTime(Instant.ofEpochMilli(cursor.getLong(stopTimeIndex))); } if (!cursor.isNull(totalDistanceIndex)) { - trackStatistics.setTotalDistance(cursor.getFloat(totalDistanceIndex)); + trackStatistics.setTotalDistance(Distance.of(cursor.getFloat(totalDistanceIndex))); } if (!cursor.isNull(totalTimeIndex)) { trackStatistics.setTotalTime(Duration.ofMillis(cursor.getLong(totalTimeIndex))); @@ -136,7 +138,7 @@ public class ContentProviderUtils { trackStatistics.setMovingTime(Duration.ofMillis(cursor.getLong(movingTimeIndex))); } if (!cursor.isNull(maxSpeedIndex)) { - trackStatistics.setMaxSpeed(cursor.getFloat(maxSpeedIndex)); + trackStatistics.setMaxSpeed(Speed.of(cursor.getFloat(maxSpeedIndex))); } if (!cursor.isNull(minAltitudeIndex)) { trackStatistics.setMinAltitude(cursor.getFloat(minAltitudeIndex)); @@ -278,12 +280,12 @@ public class ContentProviderUtils { if (trackStatistics.getStopTime() != null) { values.put(TracksColumns.STOPTIME, trackStatistics.getStopTime().toEpochMilli()); } - values.put(TracksColumns.TOTALDISTANCE, trackStatistics.getTotalDistance()); + values.put(TracksColumns.TOTALDISTANCE, trackStatistics.getTotalDistance().toM()); values.put(TracksColumns.TOTALTIME, trackStatistics.getTotalTime().toMillis()); values.put(TracksColumns.MOVINGTIME, trackStatistics.getMovingTime().toMillis()); - values.put(TracksColumns.AVGSPEED, trackStatistics.getAverageSpeed()); - values.put(TracksColumns.AVGMOVINGSPEED, trackStatistics.getAverageMovingSpeed()); - values.put(TracksColumns.MAXSPEED, trackStatistics.getMaxSpeed()); + values.put(TracksColumns.AVGSPEED, trackStatistics.getAverageSpeed().toMPS()); + values.put(TracksColumns.AVGMOVINGSPEED, trackStatistics.getAverageMovingSpeed().toMPS()); + values.put(TracksColumns.MAXSPEED, trackStatistics.getMaxSpeed().toMPS()); values.put(TracksColumns.MIN_ALTITUDE, trackStatistics.getMinAltitude()); values.put(TracksColumns.MAX_ALTITUDE, trackStatistics.getMaxAltitude()); values.put(TracksColumns.ALTITUDE_GAIN, trackStatistics.getTotalAltitudeGain()); @@ -343,7 +345,7 @@ public class ContentProviderUtils { marker.setIcon(cursor.getString(iconIndex)); } if (!cursor.isNull(lengthIndex)) { - marker.setLength(cursor.getFloat(lengthIndex)); + marker.setLength(Distance.of(cursor.getFloat(lengthIndex))); } if (!cursor.isNull(durationIndex)) { marker.setDuration(Duration.ofMillis(cursor.getLong(durationIndex))); @@ -480,7 +482,7 @@ public class ContentProviderUtils { values.put(MarkerColumns.CATEGORY, marker.getCategory()); values.put(MarkerColumns.ICON, marker.getIcon()); values.put(MarkerColumns.TRACKID, marker.getTrackId().getId()); - values.put(MarkerColumns.LENGTH, marker.getLength()); + values.put(MarkerColumns.LENGTH, marker.getLength().toM()); values.put(MarkerColumns.DURATION, marker.getDuration().toMillis()); values.put(MarkerColumns.LONGITUDE, (int) (marker.getLongitude() * 1E6)); @@ -543,7 +545,7 @@ public class ContentProviderUtils { trackPoint.setAccuracy(cursor.getFloat(indexes.accuracyIndex)); } if (!cursor.isNull(indexes.speedIndex)) { - trackPoint.setSpeed(cursor.getFloat(indexes.speedIndex)); + trackPoint.setSpeed(Speed.of(cursor.getFloat(indexes.speedIndex))); } if (!cursor.isNull(indexes.bearingIndex)) { trackPoint.setBearing(cursor.getFloat(indexes.bearingIndex)); @@ -556,7 +558,7 @@ public class ContentProviderUtils { trackPoint.setCyclingCadence_rpm(cursor.getFloat(indexes.sensorCadenceIndex)); } if (!cursor.isNull(indexes.sensorDistanceIndex)) { - trackPoint.setSensorDistance(cursor.getFloat(indexes.sensorDistanceIndex)); + trackPoint.setSensorDistance(Distance.of(cursor.getFloat(indexes.sensorDistanceIndex))); } if (!cursor.isNull(indexes.sensorPowerIndex)) { trackPoint.setPower(cursor.getFloat(indexes.sensorPowerIndex)); @@ -694,7 +696,7 @@ public class ContentProviderUtils { values.put(TrackPointsColumns.ACCURACY, trackPoint.getAccuracy()); } if (trackPoint.hasSpeed()) { - values.put(TrackPointsColumns.SPEED, trackPoint.getSpeed()); + values.put(TrackPointsColumns.SPEED, trackPoint.getSpeed().toMPS()); } if (trackPoint.hasBearing()) { values.put(TrackPointsColumns.BEARING, trackPoint.getBearing()); @@ -707,7 +709,7 @@ public class ContentProviderUtils { values.put(TrackPointsColumns.SENSOR_CADENCE, trackPoint.getCyclingCadence_rpm()); } if (trackPoint.hasSensorDistance()) { - values.put(TrackPointsColumns.SENSOR_DISTANCE, trackPoint.getSensorDistance()); + values.put(TrackPointsColumns.SENSOR_DISTANCE, trackPoint.getSensorDistance().toM()); } if (trackPoint.hasPower()) { values.put(TrackPointsColumns.SENSOR_POWER, trackPoint.getPower()); 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 6b3cfb43a..a5b6cfabd 100644 --- a/src/main/java/de/dennisguse/opentracks/content/sensor/SensorDataCycling.java +++ b/src/main/java/de/dennisguse/opentracks/content/sensor/SensorDataCycling.java @@ -6,6 +6,10 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.core.util.Pair; +import java.time.Duration; + +import de.dennisguse.opentracks.content.data.Distance; +import de.dennisguse.opentracks.content.data.Speed; import de.dennisguse.opentracks.util.UintUtils; import de.dennisguse.opentracks.util.UnitConversions; @@ -122,21 +126,21 @@ public final class SensorDataCycling { 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) { + Duration timeDiff = Duration.ofMillis((long) timeDiff_ms); + if (timeDiff.isZero() || timeDiff.isNegative()) { Log.e(TAG, "Timestamps difference is invalid: cannot compute cadence."); value = null; } else { long wheelDiff = UintUtils.diff(wheelRevolutionsCount, previous.wheelRevolutionsCount, UintUtils.UINT16_MAX); - wheelDiff = Math.abs(wheelDiff); //HACK for Garmin Speed 2 as it some of those seem to count backwards + wheelDiff = Math.abs(wheelDiff); //HACK for Garmin Speed 2 as some of those seem to count backwards - double timeDiff_s = timeDiff_ms * UnitConversions.MS_TO_S; - float distance_m = (float) (wheelDiff * wheel_circumference_mm * UnitConversions.MM_TO_M); - float distance_overall_m = distance_m; + Distance distance = Distance.of(wheelDiff * wheel_circumference_mm * UnitConversions.MM_TO_M); + Distance distanceOverall = distance; if (previous.hasValue()) { - distance_overall_m += previous.getValue().distance_overall_m; + distanceOverall = distance.plus(previous.getValue().distanceOverall); } - float speed_mps = (float) (distance_m / timeDiff_s); - value = new Data(distance_m, distance_overall_m, speed_mps); + Speed speed_mps = Speed.of(distance, timeDiff); + value = new Data(distance, distanceOverall, speed_mps); } } } @@ -144,7 +148,7 @@ public final class SensorDataCycling { @Override public void reset() { if (value != null) { - value = new Data(value.distance_m, 0, value.speed_mps); + value = new Data(value.distance, Distance.of(0), value.speed); } } @@ -167,22 +171,22 @@ public final class SensorDataCycling { } public static class Data { - public final float distance_m; - public final float distance_overall_m; - public final float speed_mps; + public final Distance distance; + public final Distance distanceOverall; + public final Speed speed; - 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; + private Data(Distance distance, Distance distanceOverall, Speed speed) { + this.distance = distance; + this.distanceOverall = distanceOverall; + this.speed = speed; } @Override public String toString() { return "Data{" + - "distance_m=" + distance_m + - ", distance_overall_m=" + distance_overall_m + - ", speed_mps=" + speed_mps + + "distance_m=" + distance + + ", distance_overall_m=" + distanceOverall + + ", speed_mps=" + speed + '}'; } } 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 cc054e663..08a956d9e 100644 --- a/src/main/java/de/dennisguse/opentracks/content/sensor/SensorDataSet.java +++ b/src/main/java/de/dennisguse/opentracks/content/sensor/SensorDataSet.java @@ -58,8 +58,8 @@ public final class SensorDataSet { } if (cyclingDistanceSpeed != null && cyclingDistanceSpeed.hasValue()) { - trackPoint.setSensorDistance(cyclingDistanceSpeed.getValue().distance_overall_m); - trackPoint.setSpeed(cyclingDistanceSpeed.getValue().speed_mps); + trackPoint.setSensorDistance(cyclingDistanceSpeed.getValue().distanceOverall); + trackPoint.setSpeed(cyclingDistanceSpeed.getValue().speed); } if (cyclingPower != null && cyclingPower.hasValue()) { diff --git a/src/main/java/de/dennisguse/opentracks/fragments/ChartFragment.java b/src/main/java/de/dennisguse/opentracks/fragments/ChartFragment.java index 019d261a1..dcacaaf7e 100644 --- a/src/main/java/de/dennisguse/opentracks/fragments/ChartFragment.java +++ b/src/main/java/de/dennisguse/opentracks/fragments/ChartFragment.java @@ -37,6 +37,7 @@ import de.dennisguse.opentracks.chart.ChartPoint; import de.dennisguse.opentracks.chart.ChartView; import de.dennisguse.opentracks.content.TrackDataHub; import de.dennisguse.opentracks.content.TrackDataListener; +import de.dennisguse.opentracks.content.data.Distance; import de.dennisguse.opentracks.content.data.Marker; import de.dennisguse.opentracks.content.data.Track; import de.dennisguse.opentracks.content.data.TrackPoint; @@ -66,7 +67,7 @@ public class ChartFragment extends Fragment implements TrackDataListener { } private SharedPreferences sharedPreferences; - private int recordingDistanceInterval; + private Distance recordingDistanceInterval; private TrackDataHub trackDataHub; @@ -346,4 +347,9 @@ public class ChartFragment extends Fragment implements TrackDataListener { void setChartByDistance(boolean value) { chartByDistance = value; } + + @VisibleForTesting + void setRecordingDistanceInterval(Distance value) { + recordingDistanceInterval = value; + } } diff --git a/src/main/java/de/dennisguse/opentracks/fragments/StatisticsRecordedFragment.java b/src/main/java/de/dennisguse/opentracks/fragments/StatisticsRecordedFragment.java index fa219ad65..92d20b58a 100644 --- a/src/main/java/de/dennisguse/opentracks/fragments/StatisticsRecordedFragment.java +++ b/src/main/java/de/dennisguse/opentracks/fragments/StatisticsRecordedFragment.java @@ -180,8 +180,7 @@ public class StatisticsRecordedFragment extends Fragment { private void updateUI() { // Set total distance { - double totalDistance = trackStatistics.getTotalDistance(); - Pair parts = StringUtils.getDistanceParts(getContext(), totalDistance, preferenceMetricUnits); + Pair parts = StringUtils.getDistanceParts(getContext(), trackStatistics.getTotalDistance(), preferenceMetricUnits); viewBinding.statsDistanceValue.setText(parts.first); viewBinding.statsDistanceUnit.setText(parts.second); @@ -201,32 +200,27 @@ public class StatisticsRecordedFragment extends Fragment { // Set average speed/pace { - double speed = trackStatistics.getAverageSpeed(); viewBinding.statsAverageSpeedLabel.setText(preferenceReportSpeed ? R.string.stats_average_speed : R.string.stats_average_pace); - Pair parts = StringUtils.getSpeedParts(getContext(), speed, preferenceMetricUnits, preferenceReportSpeed); + Pair parts = StringUtils.getSpeedParts(getContext(), trackStatistics.getAverageSpeed(), preferenceMetricUnits, preferenceReportSpeed); viewBinding.statsAverageSpeedValue.setText(parts.first); viewBinding.statsAverageSpeedUnit.setText(parts.second); } // Set max speed/pace { - double speed = trackStatistics.getMaxSpeed(); - viewBinding.statsMaxSpeedLabel.setText(preferenceReportSpeed ? R.string.stats_max_speed : R.string.stats_fastest_pace); - Pair parts = StringUtils.getSpeedParts(getContext(), speed, preferenceMetricUnits, preferenceReportSpeed); + Pair parts = StringUtils.getSpeedParts(getContext(), trackStatistics.getMaxSpeed(), preferenceMetricUnits, preferenceReportSpeed); viewBinding.statsMaxSpeedValue.setText(parts.first); viewBinding.statsMaxSpeedUnit.setText(parts.second); } // Set moving speed/pace { - double speed = trackStatistics.getAverageMovingSpeed(); - viewBinding.statsMovingSpeedLabel.setText(preferenceReportSpeed ? R.string.stats_average_moving_speed : R.string.stats_average_moving_pace); - Pair parts = StringUtils.getSpeedParts(getContext(), speed, preferenceMetricUnits, preferenceReportSpeed); + Pair parts = StringUtils.getSpeedParts(getContext(), trackStatistics.getAverageMovingSpeed(), preferenceMetricUnits, preferenceReportSpeed); viewBinding.statsMovingSpeedValue.setText(parts.first); viewBinding.statsMovingSpeedUnit.setText(parts.second); } diff --git a/src/main/java/de/dennisguse/opentracks/fragments/StatisticsRecordingFragment.java b/src/main/java/de/dennisguse/opentracks/fragments/StatisticsRecordingFragment.java index 7786620fe..44de7b92b 100644 --- a/src/main/java/de/dennisguse/opentracks/fragments/StatisticsRecordingFragment.java +++ b/src/main/java/de/dennisguse/opentracks/fragments/StatisticsRecordingFragment.java @@ -24,7 +24,9 @@ import de.dennisguse.opentracks.TrackRecordingActivity; import de.dennisguse.opentracks.adapters.SensorsAdapter; import de.dennisguse.opentracks.content.TrackDataHub; import de.dennisguse.opentracks.content.TrackDataListener; +import de.dennisguse.opentracks.content.data.Distance; import de.dennisguse.opentracks.content.data.Marker; +import de.dennisguse.opentracks.content.data.Speed; import de.dennisguse.opentracks.content.data.Track; import de.dennisguse.opentracks.content.data.TrackPoint; import de.dennisguse.opentracks.content.sensor.SensorDataCycling; @@ -371,18 +373,18 @@ public class StatisticsRecordingFragment extends Fragment implements TrackDataLi SensorDataCycling.DistanceSpeed data = sensorDataSet.getCyclingDistanceSpeed(); if (data.hasValue() && data.isRecent()) { - setTotalDistance(data.getValue().distance_overall_m); - setSpeed(data.getValue().speed_mps); + setTotalDistance(data.getValue().distanceOverall); + setSpeed(data.getValue().speed); } if (data.hasValue() && data.isRecent()) { - setSpeed(data.getValue().speed_mps); + setSpeed(data.getValue().speed); } } } private void updateStats() { - setTotalDistance(0); + setTotalDistance(Distance.of(0)); //TODO Why? // Set activity type { @@ -399,7 +401,7 @@ public class StatisticsRecordingFragment extends Fragment implements TrackDataLi // Set average speed/pace { - double speed = lastTrackStatistics != null ? lastTrackStatistics.getAverageSpeed() : Double.NaN; + Speed speed = lastTrackStatistics != null ? lastTrackStatistics.getAverageSpeed() : null; viewBinding.statsAverageSpeedLabel.setText(preferenceReportSpeed ? R.string.stats_average_speed : R.string.stats_average_pace); Pair parts = StringUtils.getSpeedParts(getContext(), speed, preferenceMetricUnits, preferenceReportSpeed); @@ -409,7 +411,7 @@ public class StatisticsRecordingFragment extends Fragment implements TrackDataLi // Set max speed/pace { - double speed = lastTrackStatistics == null ? Double.NaN : lastTrackStatistics.getMaxSpeed(); + Speed speed = lastTrackStatistics != null ? lastTrackStatistics.getMaxSpeed() : null; viewBinding.statsMaxSpeedLabel.setText(preferenceReportSpeed ? R.string.stats_max_speed : R.string.stats_fastest_pace); @@ -420,7 +422,7 @@ public class StatisticsRecordingFragment extends Fragment implements TrackDataLi // Set moving speed/pace { - double speed = lastTrackStatistics != null ? lastTrackStatistics.getAverageMovingSpeed() : Double.NaN; + Speed speed = lastTrackStatistics != null ? lastTrackStatistics.getAverageMovingSpeed() : null; viewBinding.statsMovingSpeedLabel.setText(preferenceReportSpeed ? R.string.stats_average_moving_speed : R.string.stats_average_moving_pace); @@ -449,7 +451,7 @@ public class StatisticsRecordingFragment extends Fragment implements TrackDataLi private void setLocationValues() { // Set speed/pace - double speed = lastTrackPoint != null && lastTrackPoint.hasSpeed() ? lastTrackPoint.getSpeed() : Double.NaN; + Speed speed = lastTrackPoint != null && lastTrackPoint.hasSpeed() ? lastTrackPoint.getSpeed() : null; setSpeed(speed); // Set altitude @@ -477,15 +479,15 @@ public class StatisticsRecordingFragment extends Fragment implements TrackDataLi } } - private void setTotalDistance(double sensorDistanceSinceLastTrackpoint) { - double totalDistance = lastTrackStatistics != null ? (lastTrackStatistics.getTotalDistance() + sensorDistanceSinceLastTrackpoint) : Double.NaN; + private void setTotalDistance(Distance sensorDistanceSinceLastTrackpoint) { + Distance totalDistance = lastTrackStatistics != null ? (lastTrackStatistics.getTotalDistance().plus(sensorDistanceSinceLastTrackpoint)) : Distance.invalid(); Pair parts = StringUtils.getDistanceParts(getContext(), totalDistance, preferenceMetricUnits); viewBinding.statsDistanceValue.setText(parts.first); viewBinding.statsDistanceUnit.setText(parts.second); } - private void setSpeed(double speed) { + private void setSpeed(Speed speed) { viewBinding.statsSpeedLabel.setText(preferenceReportSpeed ? R.string.stats_speed : R.string.stats_pace); Pair parts = StringUtils.getSpeedParts(getContext(), speed, preferenceMetricUnits, preferenceReportSpeed); 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 3b60560db..44b7a4fe2 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 @@ -290,7 +290,7 @@ public class GPXTrackExporter implements TrackExporter { printWriter.println(""); if (trackPoint.hasSpeed()) { - printWriter.println("" + SPEED_FORMAT.format(trackPoint.getSpeed()) + ""); + printWriter.println("" + SPEED_FORMAT.format(trackPoint.getSpeed().toMPS()) + ""); } if (trackPoint.hasHeartRate()) { 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 4f288d181..d869d5724 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 @@ -395,10 +395,10 @@ public class KMLTrackExporter implements TrackExporter { } else { printWriter.println(""); } - speedList.add(trackPoint.hasSpeed() ? trackPoint.getSpeed() : null); + speedList.add(trackPoint.hasSpeed() ? (float) trackPoint.getSpeed().toMPS() : null); if (exportSensorData) { - distanceList.add(trackPoint.hasSensorDistance() ? trackPoint.getSensorDistance() : null); + distanceList.add(trackPoint.hasSensorDistance() ? (float) trackPoint.getSensorDistance().toM() : 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 326c6951d..8a9483544 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 @@ -42,11 +42,14 @@ import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.SAXParserFactory; import de.dennisguse.opentracks.R; +import de.dennisguse.opentracks.content.data.Distance; import de.dennisguse.opentracks.content.data.Marker; +import de.dennisguse.opentracks.content.data.Speed; import de.dennisguse.opentracks.content.data.Track; import de.dennisguse.opentracks.content.data.TrackPoint; import de.dennisguse.opentracks.content.provider.ContentProviderUtils; import de.dennisguse.opentracks.content.provider.TrackPointIterator; +import de.dennisguse.opentracks.stats.TrackStatistics; import de.dennisguse.opentracks.stats.TrackStatisticsUpdater; import de.dennisguse.opentracks.util.FileUtils; import de.dennisguse.opentracks.util.LocationUtils; @@ -65,7 +68,7 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track private final Context context; private final ContentProviderUtils contentProviderUtils; - private final int recordingDistanceInterval; + private final Distance recordingDistanceInterval; private final List trackIds = new ArrayList<>(); private final List markers = new ArrayList<>(); @@ -202,11 +205,10 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track if (trackPoint.getLatitude() == marker.getLatitude() && trackPoint.getLongitude() == marker.getLongitude()) { String markerDescription = marker.getDescription(); String icon = context.getString(R.string.marker_icon_url); - double length = trackStatisticsUpdater.getTrackStatistics().getTotalDistance(); - Duration duration = trackStatisticsUpdater.getTrackStatistics().getTotalTime(); + TrackStatistics stats = trackStatisticsUpdater.getTrackStatistics(); // Insert marker - Marker newMarker = new Marker(marker.getName(), markerDescription, marker.getCategory(), icon, track.getId(), length, duration, trackPoint, marker.getPhotoUrl()); + Marker newMarker = new Marker(marker.getName(), markerDescription, marker.getCategory(), icon, track.getId(), stats, trackPoint, marker.getPhotoUrl()); contentProviderUtils.insertMarker(newMarker); } @@ -374,17 +376,16 @@ 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.distanceToPrevious(trackPoint) / timeDifference.toMillis(); - trackPoint.setSpeed(speed); + trackPoint.setSpeed(Speed.of(trackData.lastLocationInCurrentSegment.distanceToPrevious(trackPoint), timeDifference)); } } } if (trackPoint.hasLocation() && trackData.lastLocationInCurrentSegment.hasLocation()) { trackPoint.setBearing(trackData.lastLocationInCurrentSegment.bearingTo(trackPoint)); - long maxRecordingDistance = PreferencesUtils.getMaxRecordingDistance(sharedPreferences, context); //TODO Should only be read once! - double distanceToLastTrackLocation = trackPoint.distanceToPrevious(trackData.lastLocationInCurrentSegment); - if (distanceToLastTrackLocation > maxRecordingDistance) { + Distance maxRecordingDistance = PreferencesUtils.getMaxRecordingDistance(sharedPreferences, context); //TODO Should only be read once! + Distance distanceToLastTrackLocation = trackPoint.distanceToPrevious(trackData.lastLocationInCurrentSegment); + if (distanceToLastTrackLocation.greaterThan(maxRecordingDistance)) { trackPoint.setType(TrackPoint.Type.SEGMENT_START_AUTOMATIC); } } @@ -460,7 +461,7 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track if (speed != null) { try { - trackPoint.setSpeed(Float.parseFloat(speed)); + trackPoint.setSpeed(Speed.of(speed)); } catch (Exception e) { throw new ParsingException(createErrorMessage(String.format(Locale.US, "Unable to parse speed: %s", speed)), e); } @@ -505,7 +506,7 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track } if (distance != null) { try { - trackPoint.setSensorDistance(Float.parseFloat(distance)); + trackPoint.setSensorDistance(Distance.of(distance)); } catch (Exception e) { throw new ParsingException(createErrorMessage(String.format(Locale.US, "Unable to parse distance: %s", distance)), e); } 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 7779e720d..cc34b3613 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 @@ -26,6 +26,8 @@ import org.xml.sax.SAXException; import java.util.ArrayList; +import de.dennisguse.opentracks.content.data.Distance; +import de.dennisguse.opentracks.content.data.Speed; import de.dennisguse.opentracks.content.data.TrackPoint; import de.dennisguse.opentracks.content.provider.ContentProviderUtils; import de.dennisguse.opentracks.io.file.exporter.KMLTrackExporter; @@ -231,11 +233,11 @@ public class KmlFileTrackImporter extends AbstractFileTrackImporter { for (int i = 0; i < trackPoints.size(); i++) { TrackPoint trackPoint = trackPoints.get(i); - if (i < speedList.size()) { - trackPoint.setSpeed(speedList.get(i)); + if (i < speedList.size() && speedList.get(i) != null) { + trackPoint.setSpeed(Speed.of(speedList.get(i))); } - if (i < distanceList.size()) { - trackPoint.setSensorDistance(distanceList.get(i)); + if (i < distanceList.size() && distanceList.get(i) != null) { + trackPoint.setSensorDistance(Distance.of(distanceList.get(i))); } if (i < heartRateList.size()) { trackPoint.setHeartRate_bpm(heartRateList.get(i)); diff --git a/src/main/java/de/dennisguse/opentracks/services/TrackRecordingService.java b/src/main/java/de/dennisguse/opentracks/services/TrackRecordingService.java index e2558d054..b52c6499f 100644 --- a/src/main/java/de/dennisguse/opentracks/services/TrackRecordingService.java +++ b/src/main/java/de/dennisguse/opentracks/services/TrackRecordingService.java @@ -40,6 +40,7 @@ import java.time.Instant; import de.dennisguse.opentracks.R; import de.dennisguse.opentracks.TrackListActivity; import de.dennisguse.opentracks.TrackRecordingActivity; +import de.dennisguse.opentracks.content.data.Distance; import de.dennisguse.opentracks.content.data.Marker; import de.dennisguse.opentracks.content.data.Track; import de.dennisguse.opentracks.content.data.TrackPoint; @@ -80,8 +81,8 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand private SharedPreferences sharedPreferences; - private int recordingDistanceInterval; - private int maxRecordingDistance; + private Distance recordingDistanceInterval; + private Distance maxRecordingDistance; private final OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() { @Override public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { @@ -237,11 +238,9 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand photoUrl = photoUrl != null ? photoUrl : ""; TrackStatistics stats = trackStatisticsUpdater.getTrackStatistics(); - double length = stats.getTotalDistance(); - Duration duration = stats.getTotalTime(); // Insert marker - Marker marker = new Marker(name, description, category, icon, serviceStatus.getRecordingTrackId(), length, duration, trackPoint, photoUrl); + Marker marker = new Marker(name, description, category, icon, serviceStatus.getRecordingTrackId(), stats, trackPoint, photoUrl); Uri uri = contentProviderUtils.insertMarker(marker); return new Marker.Id(ContentUris.parseId(uri)); } @@ -511,8 +510,8 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand return; } - double distanceToLastTrackLocation = trackPoint.distanceToPrevious(lastValidTrackPoint); - if (distanceToLastTrackLocation > maxRecordingDistance) { + Distance distanceToLastTrackLocation = trackPoint.distanceToPrevious(lastValidTrackPoint); + if (distanceToLastTrackLocation.greaterThan(maxRecordingDistance)) { insertTrackPointIfNewer(track, lastTrackPoint); trackPoint.setType(TrackPoint.Type.SEGMENT_START_AUTOMATIC); @@ -523,7 +522,7 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand return; } - if (trackPoint.hasSensorData() || distanceToLastTrackLocation >= recordingDistanceInterval) { + if (trackPoint.hasSensorData() || distanceToLastTrackLocation.greaterOrEqualThan(recordingDistanceInterval)) { insertTrackPointIfNewer(track, lastTrackPoint); insertTrackPoint(track, trackPoint); diff --git a/src/main/java/de/dennisguse/opentracks/services/TrackRecordingServiceNotificationManager.java b/src/main/java/de/dennisguse/opentracks/services/TrackRecordingServiceNotificationManager.java index b611ed3e4..9519ad2e1 100644 --- a/src/main/java/de/dennisguse/opentracks/services/TrackRecordingServiceNotificationManager.java +++ b/src/main/java/de/dennisguse/opentracks/services/TrackRecordingServiceNotificationManager.java @@ -11,6 +11,7 @@ import androidx.annotation.VisibleForTesting; import androidx.core.app.NotificationCompat; import de.dennisguse.opentracks.R; +import de.dennisguse.opentracks.content.data.Distance; import de.dennisguse.opentracks.content.data.TrackPoint; import de.dennisguse.opentracks.stats.TrackStatistics; import de.dennisguse.opentracks.util.StringUtils; @@ -68,7 +69,7 @@ class TrackRecordingServiceNotificationManager { void updateTrackPoint(Context context, TrackStatistics trackStatistics, TrackPoint trackPoint, int recordingGpsAccuracy) { String formattedAccuracy = context.getString(R.string.value_none); if (trackPoint.hasAccuracy()) { - formattedAccuracy = StringUtils.formatDistance(context, trackPoint.getAccuracy(), metricUnits); + formattedAccuracy = StringUtils.formatDistance(context, Distance.of(trackPoint.getAccuracy()), metricUnits); boolean currentLocationWasAccurate = trackPoint.getAccuracy() < recordingGpsAccuracy; boolean shouldAlert = !currentLocationWasAccurate && previousLocationWasAccurate; diff --git a/src/main/java/de/dennisguse/opentracks/services/handlers/GpsStatus.java b/src/main/java/de/dennisguse/opentracks/services/handlers/GpsStatus.java index 55efbf716..ae59eb1c7 100644 --- a/src/main/java/de/dennisguse/opentracks/services/handlers/GpsStatus.java +++ b/src/main/java/de/dennisguse/opentracks/services/handlers/GpsStatus.java @@ -25,7 +25,7 @@ class GpsStatus { private static final Duration SIGNAL_LOST_THRESHOLD = Duration.ofSeconds(10); // Threshold for accuracy. - private double signalBadThreshold; + private double signalBadThreshold; //TODO Distance? // Threshold for time without points. private Duration signalLostThreshold; @@ -67,7 +67,7 @@ class GpsStatus { this.context = context; SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context); - signalBadThreshold = PreferencesUtils.getRecordingDistanceInterval(sharedPreferences, context); + signalBadThreshold = PreferencesUtils.getRecordingDistanceInterval(sharedPreferences, context).toM(); Duration minRecordingInterval = Duration.ofSeconds(PreferencesUtils.getMinRecordingInterval(sharedPreferences, context)); signalLostThreshold = !minRecordingInterval.isNegative() ? SIGNAL_LOST_THRESHOLD.plus(minRecordingInterval) : SIGNAL_LOST_THRESHOLD; diff --git a/src/main/java/de/dennisguse/opentracks/services/handlers/LocationHandler.java b/src/main/java/de/dennisguse/opentracks/services/handlers/LocationHandler.java index b2bbcf5f5..d4227e313 100644 --- a/src/main/java/de/dennisguse/opentracks/services/handlers/LocationHandler.java +++ b/src/main/java/de/dennisguse/opentracks/services/handlers/LocationHandler.java @@ -78,7 +78,7 @@ class LocationHandler implements HandlerServer.Handler, LocationListener, GpsSta } if (PreferencesUtils.isKey(context, R.string.recording_distance_interval_key, key)) { if (gpsStatus != null) { - gpsStatus.onRecordingDistanceChanged(PreferencesUtils.getRecordingDistanceInterval(sharedPreferences, context)); + gpsStatus.onRecordingDistanceChanged((int) PreferencesUtils.getRecordingDistanceInterval(sharedPreferences, context).toM()); //TODO Use Distance? } } } diff --git a/src/main/java/de/dennisguse/opentracks/services/tasks/PeriodicTaskExecutor.java b/src/main/java/de/dennisguse/opentracks/services/tasks/PeriodicTaskExecutor.java index e0332997b..2403dcb2a 100644 --- a/src/main/java/de/dennisguse/opentracks/services/tasks/PeriodicTaskExecutor.java +++ b/src/main/java/de/dennisguse/opentracks/services/tasks/PeriodicTaskExecutor.java @@ -20,9 +20,9 @@ import android.util.Log; import java.time.Duration; import de.dennisguse.opentracks.R; +import de.dennisguse.opentracks.content.data.Distance; import de.dennisguse.opentracks.services.TrackRecordingService; import de.dennisguse.opentracks.stats.TrackStatistics; -import de.dennisguse.opentracks.util.UnitConversions; /** * Execute a periodic task on a time or distance schedule. @@ -128,12 +128,9 @@ public class PeriodicTaskExecutor { return; } - double distance = trackStatistics.getTotalDistance() * UnitConversions.M_TO_KM; - if (!metricUnits) { - distance *= UnitConversions.KM_TO_MI; - } + Distance distance = trackStatistics.getTotalDistance(); - if (distance > nextTaskDistance) { + if (distance.greaterThan(Distance.of(nextTaskDistance))) { periodicTask.run(trackRecordingService); calculateNextTaskDistance(); } @@ -178,10 +175,7 @@ public class PeriodicTaskExecutor { return; } - double distance = trackStatistics.getTotalDistance() * UnitConversions.M_TO_KM; - if (!metricUnits) { - distance *= UnitConversions.KM_TO_MI; - } + double distance = trackStatistics.getTotalDistance().to(metricUnits); // The index will be negative since the frequency is negative. int index = (int) (distance / taskFrequency); diff --git a/src/main/java/de/dennisguse/opentracks/settings/PreferenceHelper.java b/src/main/java/de/dennisguse/opentracks/settings/PreferenceHelper.java index e1b0c7cd1..2015c7a3f 100644 --- a/src/main/java/de/dennisguse/opentracks/settings/PreferenceHelper.java +++ b/src/main/java/de/dennisguse/opentracks/settings/PreferenceHelper.java @@ -32,7 +32,7 @@ final class PreferenceHelper { String[] entryValues = context.getResources().getStringArray(R.array.recording_distance_interval_values); String[] entries = new String[entryValues.length]; - final int recordingDistanceIntervalDefault = PreferencesUtils.getRecordingDistanceIntervalDefault(context); + final int recordingDistanceIntervalDefault = (int) PreferencesUtils.getRecordingDistanceIntervalDefault(context).toM(); for (int i = 0; i < entryValues.length; i++) { int value = Integer.parseInt(entryValues[i]); diff --git a/src/main/java/de/dennisguse/opentracks/stats/TrackStatistics.java b/src/main/java/de/dennisguse/opentracks/stats/TrackStatistics.java index 291a4a6c8..0edfbd5dc 100644 --- a/src/main/java/de/dennisguse/opentracks/stats/TrackStatistics.java +++ b/src/main/java/de/dennisguse/opentracks/stats/TrackStatistics.java @@ -22,6 +22,9 @@ import androidx.annotation.Nullable; import java.time.Duration; import java.time.Instant; +import de.dennisguse.opentracks.content.data.Distance; +import de.dennisguse.opentracks.content.data.Speed; + /** * Statistical data about a {@link de.dennisguse.opentracks.content.data.Track}. * The data in this class should be filled out by {@link TrackStatisticsUpdater}. @@ -40,13 +43,13 @@ public class TrackStatistics { // The track stop time. private Instant stopTime; - private double totalDistance_m; + private Distance totalDistance; // Updated when new points are received, may be stale. private Duration totalTime; // Based on when we believe the user is traveling. private Duration movingTime; // The maximum speed (meters/second) that we believe is valid. - private double maxSpeed_mps; + private Speed maxSpeed; private Float totalAltitudeGain_m = null; private Float totalAltitudeLoss_m = null; @@ -62,10 +65,10 @@ public class TrackStatistics { public TrackStatistics(TrackStatistics other) { startTime = other.startTime; stopTime = other.stopTime; - totalDistance_m = other.totalDistance_m; + totalDistance = other.totalDistance; totalTime = other.totalTime; movingTime = other.movingTime; - maxSpeed_mps = other.maxSpeed_mps; + maxSpeed = other.maxSpeed; altitudeExtremities.set(other.altitudeExtremities.getMin(), other.altitudeExtremities.getMax()); totalAltitudeGain_m = other.totalAltitudeGain_m; totalAltitudeLoss_m = other.totalAltitudeLoss_m; @@ -89,10 +92,10 @@ public class TrackStatistics { stopTime = stopTime.isAfter(other.stopTime) ? stopTime : other.stopTime; } - totalDistance_m += other.totalDistance_m; + totalDistance = totalDistance.plus(other.totalDistance); totalTime = totalTime.plus(other.totalTime); movingTime = movingTime.plus(other.movingTime); - maxSpeed_mps = Math.max(maxSpeed_mps, other.maxSpeed_mps); + maxSpeed = Speed.max(maxSpeed, other.maxSpeed); if (other.altitudeExtremities.hasData()) { altitudeExtremities.update(other.altitudeExtremities.getMin()); altitudeExtremities.update(other.altitudeExtremities.getMax()); @@ -121,10 +124,10 @@ public class TrackStatistics { startTime = null; stopTime = null; - setTotalDistance(0); + setTotalDistance(Distance.of(0)); setTotalTime(Duration.ofSeconds(0)); setMovingTime(Duration.ofSeconds(0)); - setMaxSpeed(0); + setMaxSpeed(Speed.zero()); setTotalAltitudeGain(null); setTotalAltitudeLoss(null); } @@ -157,16 +160,16 @@ public class TrackStatistics { this.stopTime = stopTime; } - public double getTotalDistance() { - return totalDistance_m; + public Distance getTotalDistance() { + return totalDistance; } - public void setTotalDistance(double totalDistance_m) { - this.totalDistance_m = totalDistance_m; + public void setTotalDistance(Distance totalDistance_m) { + this.totalDistance = totalDistance_m; } - public void addTotalDistance(double distance_m) { - totalDistance_m += distance_m; + public void addTotalDistance(Distance distance_m) { + totalDistance = totalDistance.plus(distance_m); } /** @@ -195,35 +198,26 @@ public class TrackStatistics { } /** - * Gets the average speed in meters/second. + * Gets the average speed. * This calculation only takes into account the displacement until the last point that was accounted for in statistics. */ - public double getAverageSpeed() { - if (totalTime.isZero()) { - return 0.0; + public Speed getAverageSpeed() { + if (totalDistance.isZero() && totalDistance.isZero()) { + return Speed.of(0); } - return totalDistance_m / (double) totalTime.getSeconds(); + return Speed.of(totalDistance.toM() / totalTime.getSeconds()); } - /** - * Gets the average moving speed in meters/second. - */ - public double getAverageMovingSpeed() { - if (movingTime.isZero()) { - return 0.0; - } - return totalDistance_m / (double) movingTime.getSeconds(); + public Speed getAverageMovingSpeed() { + return Speed.of(totalDistance, movingTime); } - /** - * Gets the maximum speed in meters/second. - */ - public double getMaxSpeed() { - return Math.max(maxSpeed_mps, getAverageMovingSpeed()); + public Speed getMaxSpeed() { + return Speed.max(maxSpeed, getAverageMovingSpeed()); } - public void setMaxSpeed(double maxSpeed_mps) { - this.maxSpeed_mps = maxSpeed_mps; + public void setMaxSpeed(Speed maxSpeed) { + this.maxSpeed = maxSpeed; } public boolean hasAltitudeMin() { diff --git a/src/main/java/de/dennisguse/opentracks/stats/TrackStatisticsUpdater.java b/src/main/java/de/dennisguse/opentracks/stats/TrackStatisticsUpdater.java index e1a27e842..629f8b535 100644 --- a/src/main/java/de/dennisguse/opentracks/stats/TrackStatisticsUpdater.java +++ b/src/main/java/de/dennisguse/opentracks/stats/TrackStatisticsUpdater.java @@ -23,8 +23,9 @@ import androidx.annotation.VisibleForTesting; import java.time.Duration; +import de.dennisguse.opentracks.content.data.Distance; +import de.dennisguse.opentracks.content.data.Speed; import de.dennisguse.opentracks.content.data.TrackPoint; -import de.dennisguse.opentracks.content.provider.TrackPointIterator; /** * Updater for {@link TrackStatistics}. @@ -103,12 +104,12 @@ public class TrackStatisticsUpdater { * @param trackPoint the trackPoint * @param minGPSDistance the min recording distance */ - public void addTrackPoint(TrackPoint trackPoint, int minGPSDistance) { + public void addTrackPoint(TrackPoint trackPoint, Distance minGPSDistance) { internalAddTrackPoint(trackPoint, minGPSDistance); Log.d(TAG, this.toString()); } - private void internalAddTrackPoint(TrackPoint trackPoint, int minGPSDistance) { + private void internalAddTrackPoint(TrackPoint trackPoint, Distance minGPSDistance) { if (!trackInitialized) { trackStatistics.setStartTime(trackPoint.getTime()); trackInitialized = true; @@ -158,8 +159,8 @@ public class TrackStatisticsUpdater { if (!trackPoint.hasSensorDistance()) { // GPS-based distance/speed - float movingDistance = lastMovingTrackPoint.distanceToPrevious(trackPoint); - if (movingDistance < minGPSDistance && !trackPoint.isMoving()) { + Distance movingDistance = lastMovingTrackPoint.distanceToPrevious(trackPoint); + if (movingDistance.lessThan(minGPSDistance) && !trackPoint.isMoving()) { speedBuffer_mps.reset(); lastTrackPoint = trackPoint; return; @@ -196,13 +197,6 @@ public class TrackStatisticsUpdater { speedBuffer_mps.reset(); } - public void addTrackPoint(TrackPointIterator iterator, int minRecordingDistance) { - while (iterator.hasNext()) { - TrackPoint location = iterator.next(); - addTrackPoint(location, minRecordingDistance); - } - } - /** * Gets the smoothed altitude over several readings. * The altitude readings is noisy so the smoothed altitude is better than the raw altitude for many tasks. @@ -211,8 +205,8 @@ public class TrackStatisticsUpdater { return altitudeBuffer_m.getAverage(); } - public double getSmoothedSpeed() { - return speedBuffer_mps.getAverage(); + public Speed getSmoothedSpeed() { + return Speed.of(speedBuffer_mps.getAverage()); } /** @@ -223,9 +217,10 @@ public class TrackStatisticsUpdater { if (!trackPoint.isMoving()) { speedBuffer_mps.reset(); } else if (isValidSpeed(trackPoint, lastTrackPoint)) { - speedBuffer_mps.setNext(trackPoint.getSpeed()); - if (speedBuffer_mps.getAverage() > currentSegment.getMaxSpeed()) { - currentSegment.setMaxSpeed(speedBuffer_mps.getAverage()); + speedBuffer_mps.setNext(trackPoint.getSpeed().toMPS()); + Speed average = Speed.of(speedBuffer_mps.getAverage()); + if (average.greaterThan(currentSegment.getMaxSpeed())) { + currentSegment.setMaxSpeed(average); } } else { Log.d(TAG, "Invalid speed. speed: " + trackPoint.getSpeed() + " lastLocationSpeed: " + lastTrackPoint.getSpeed()); @@ -243,27 +238,26 @@ public class TrackStatisticsUpdater { private boolean isValidSpeed(@NonNull TrackPoint trackPoint, @NonNull TrackPoint lastTrackPoint) { // There are a lot of noisy speed readings. Do the cheapest checks first, most expensive last. - if (trackPoint.getSpeed() == 0) { + if (trackPoint.getSpeed().isZero()) { return false; } - // The following code will ignore unlikely readings. 128 m/s seems to be an internal android error code. - if (Math.abs(trackPoint.getSpeed() - 128) < 1) { - return false; - } - - // See if the speed seems physically likely. Ignore any speeds that imply acceleration greater than 2g. Duration timeDifference = Duration.between(lastTrackPoint.getTime(), trackPoint.getTime()); - double speedDifference = Math.abs(lastTrackPoint.getSpeed() - trackPoint.getSpeed()); - if (speedDifference > MAX_ACCELERATION * timeDifference.toMillis()) { - return false; + Speed maxAcceleration = Speed.of(MAX_ACCELERATION * timeDifference.toMillis()); + { + // See if the speed seems physically likely. Ignore any speeds that imply acceleration greater than 2g. + Speed speedDifference = Speed.absDiff(lastTrackPoint.getSpeed(), trackPoint.getSpeed()); + if (speedDifference.greaterThan(maxAcceleration)) { + return false; + } } // Only check if the speed buffer is full. Check that the speed is less than 10X the smoothed average and the speed difference doesn't imply 2g acceleration. if (speedBuffer_mps.isFull()) { - double average = speedBuffer_mps.getAverage(); - double diff = Math.abs(average - trackPoint.getSpeed()); - return (trackPoint.getSpeed() < average * 10) && (diff < MAX_ACCELERATION * timeDifference.toMillis()); + Speed average = Speed.of(speedBuffer_mps.getAverage()); + Speed speedDifference = Speed.absDiff(average, trackPoint.getSpeed()); + + return trackPoint.getSpeed().lessThan(average.mul(10)) && speedDifference.lessThan(maxAcceleration); } return true; diff --git a/src/main/java/de/dennisguse/opentracks/util/AnnouncementUtils.java b/src/main/java/de/dennisguse/opentracks/util/AnnouncementUtils.java index 4fdd8018b..c3a891d3a 100644 --- a/src/main/java/de/dennisguse/opentracks/util/AnnouncementUtils.java +++ b/src/main/java/de/dennisguse/opentracks/util/AnnouncementUtils.java @@ -7,6 +7,8 @@ import androidx.annotation.Nullable; import java.time.Duration; import de.dennisguse.opentracks.R; +import de.dennisguse.opentracks.content.data.Distance; +import de.dennisguse.opentracks.content.data.Speed; import de.dennisguse.opentracks.stats.TrackStatistics; import de.dennisguse.opentracks.viewmodels.IntervalStatistics; @@ -16,17 +18,15 @@ public class AnnouncementUtils { } public static String getAnnouncement(Context context, TrackStatistics trackStatistics, boolean isMetricUnits, boolean isReportSpeed, @Nullable IntervalStatistics.Interval currentInterval) { - double distance = trackStatistics.getTotalDistance() * UnitConversions.M_TO_KM; - double distancePerTime = trackStatistics.getAverageMovingSpeed() * UnitConversions.MPS_TO_KMH; - double currentDistancePerTime = currentInterval != null ? currentInterval.getSpeed_ms() * UnitConversions.MPS_TO_KMH : 0; + Distance distance = trackStatistics.getTotalDistance(); + Speed distancePerTime = trackStatistics.getAverageMovingSpeed(); + double currentDistancePerTime = currentInterval != null ? currentInterval.getSpeed_ms() * UnitConversions.MPS_TO_KMH : 0; //TODO Use Speed? - if (distance == 0) { + if (distance.isZero()) { return context.getString(R.string.voice_total_distance_zero); } if (!isMetricUnits) { - distance *= UnitConversions.KM_TO_MI; - distancePerTime *= UnitConversions.KM_TO_MI; currentDistancePerTime *= UnitConversions.KM_TO_MI; } @@ -35,24 +35,27 @@ public class AnnouncementUtils { String currentRateMsg; if (isReportSpeed) { int speedId = isMetricUnits ? R.plurals.voiceSpeedKilometersPerHour : R.plurals.voiceSpeedMilesPerHour; - rate = context.getResources().getQuantityString(speedId, getQuantityCount(distancePerTime), distancePerTime); + double distanceInUnit = distancePerTime.to(isMetricUnits); + rate = context.getResources().getQuantityString(speedId, getQuantityCount(distanceInUnit), distanceInUnit); currentRate = context.getResources().getQuantityString(speedId, getQuantityCount(currentDistancePerTime), currentDistancePerTime); currentRateMsg = context.getString(R.string.voice_speed_lap, currentRate); } else { - double timePerDistance = distancePerTime == 0 ? 0.0 : 1 / distancePerTime; + double timePerDistance = distancePerTime.isZero() ? 0.0 : 1 / distancePerTime.to(isMetricUnits); //TODO PACE + int paceId = isMetricUnits ? R.string.voice_pace_per_kilometer : R.string.voice_pace_per_mile; Duration time = Duration.ofMillis((long) (timePerDistance * UnitConversions.HR_TO_MIN * UnitConversions.MIN_TO_S * UnitConversions.S_TO_MS)); rate = context.getString(paceId, getAnnounceTime(context, time)); - double currentTimePerDistance = currentDistancePerTime == 0 ? 0.0 : 1 / currentDistancePerTime; + double currentTimePerDistance = currentDistancePerTime == 0 ? 0.0 : 1 / currentDistancePerTime; //TODO PACE Duration currentTime = Duration.ofMillis((long) (currentTimePerDistance * UnitConversions.HR_TO_MIN * UnitConversions.MIN_TO_S * UnitConversions.S_TO_MS)); currentRate = context.getString(paceId, getAnnounceTime(context, currentTime)); currentRateMsg = context.getString(R.string.voice_pace_lap, currentRate); } int totalDistanceId = isMetricUnits ? R.plurals.voiceTotalDistanceKilometers : R.plurals.voiceTotalDistanceMiles; - String totalDistance = context.getResources().getQuantityString(totalDistanceId, getQuantityCount(distance), distance); + double distanceInUnit = distance.to(isMetricUnits); + String totalDistance = context.getResources().getQuantityString(totalDistanceId, getQuantityCount(distanceInUnit), distanceInUnit); currentRateMsg = currentInterval == null ? "" : " " + currentRateMsg; diff --git a/src/main/java/de/dennisguse/opentracks/util/PreferencesUtils.java b/src/main/java/de/dennisguse/opentracks/util/PreferencesUtils.java index 2b1893101..6807a324e 100644 --- a/src/main/java/de/dennisguse/opentracks/util/PreferencesUtils.java +++ b/src/main/java/de/dennisguse/opentracks/util/PreferencesUtils.java @@ -27,6 +27,7 @@ import androidx.documentfile.provider.DocumentFile; import androidx.preference.PreferenceManager; import de.dennisguse.opentracks.R; +import de.dennisguse.opentracks.content.data.Distance; import de.dennisguse.opentracks.io.file.TrackFileFormat; /** @@ -180,17 +181,21 @@ public class PreferencesUtils { return getInt(sharedPreferences, context, R.string.voice_frequency_key, VOICE_FREQUENCY_DEFAULT); } - public static int getRecordingDistanceInterval(SharedPreferences sharedPreferences, Context context) { - return getInt(sharedPreferences, context, R.string.recording_distance_interval_key, getRecordingDistanceIntervalDefault(context)); + public static Distance getRecordingDistanceInterval(SharedPreferences sharedPreferences, Context context) { + return Distance.of(getInt(sharedPreferences, context, R.string.recording_distance_interval_key, getRecordingDistanceIntervalDefaultInternal(context))); } - public static int getRecordingDistanceIntervalDefault(Context context) { + public static Distance getRecordingDistanceIntervalDefault(Context context) { + return Distance.of(getRecordingDistanceIntervalDefaultInternal(context)); + } + + private static int getRecordingDistanceIntervalDefaultInternal(Context context) { return Integer.parseInt(context.getResources().getString(R.string.recording_distance_interval_default)); } - public static int getMaxRecordingDistance(SharedPreferences sharedPreferences, Context context) { + public static Distance getMaxRecordingDistance(SharedPreferences sharedPreferences, Context context) { final int MAX_RECORDING_DISTANCE = Integer.parseInt(context.getResources().getString(R.string.max_recording_distance_default)); - return getInt(sharedPreferences, context, R.string.max_recording_distance_key, MAX_RECORDING_DISTANCE); + return Distance.of(getInt(sharedPreferences, context, R.string.max_recording_distance_key, MAX_RECORDING_DISTANCE)); } //TODO Duration diff --git a/src/main/java/de/dennisguse/opentracks/util/StringUtils.java b/src/main/java/de/dennisguse/opentracks/util/StringUtils.java index 0ee3c1823..60f5b90e6 100644 --- a/src/main/java/de/dennisguse/opentracks/util/StringUtils.java +++ b/src/main/java/de/dennisguse/opentracks/util/StringUtils.java @@ -36,6 +36,8 @@ import java.time.temporal.TemporalAccessor; import java.util.Locale; import de.dennisguse.opentracks.R; +import de.dennisguse.opentracks.content.data.Distance; +import de.dennisguse.opentracks.content.data.Speed; /** * Various string manipulation methods. @@ -86,50 +88,38 @@ public class StringUtils { * Formats the distance in meters. * * @param context the context - * @param distance_m the distance_m + * @param distance the distance * @param metricUnits true to use metric units. False to use imperial units */ - public static String formatDistance(Context context, double distance_m, boolean metricUnits) { - if (Double.isNaN(distance_m) || Double.isInfinite(distance_m)) { + public static String formatDistance(Context context, Distance distance, boolean metricUnits) { + if (distance.isInvalid()) { return context.getString(R.string.value_unknown); } if (metricUnits) { - if (distance_m > 500.0) { - distance_m *= UnitConversions.M_TO_KM; - return context.getString(R.string.value_float_kilometer, distance_m); + if (distance.greaterThan(Distance.of(500))) { + return context.getString(R.string.value_float_kilometer, distance.toKM()); } else { - return context.getString(R.string.value_float_meter, distance_m); + return context.getString(R.string.value_float_meter, distance.toM()); } } else { - if (distance_m * UnitConversions.M_TO_MI > 0.5) { - distance_m *= UnitConversions.M_TO_MI; - return context.getString(R.string.value_float_mile, distance_m); + if (distance.greaterThan(Distance.ofMile(0.5))) { + return context.getString(R.string.value_float_mile, distance.toMI()); } else { - distance_m *= UnitConversions.M_TO_FT; - return context.getString(R.string.value_float_feet, distance_m); + return context.getString(R.string.value_float_feet, distance.toFT()); } } } - public static String formatSpeed(Context context, double speed_mps, boolean metricUnits, boolean reportSpeed) { - if (Double.isNaN(speed_mps) || Double.isInfinite(speed_mps)) { - speed_mps = 0; - } - - double speed = speed_mps * UnitConversions.M_TO_KM; //KM p sec - if (!metricUnits) { - speed *= UnitConversions.KM_TO_MI; - } - + public static String formatSpeed(Context context, Speed speed, boolean metricUnits, boolean reportSpeed) { if (reportSpeed) { if (metricUnits) { - return context.getString(R.string.value_float_kilometer_hour, speed * UnitConversions.S_TO_HR); + return context.getString(R.string.value_float_kilometer_hour, speed.toKMH()); } else { - return context.getString(R.string.value_float_mile_hour, speed * UnitConversions.S_TO_HR); + return context.getString(R.string.value_float_mile_hour, speed.toMPH()); } } else { - int pace = speed == 0 ? 0 : (int) Math.round(1 / speed); //sec / [KM | MI] + int pace = (int) speed.toPace(metricUnits).getSeconds(); int minutes = pace / 60; int seconds = pace % 60; if (metricUnits) { @@ -167,45 +157,40 @@ public class StringUtils { * Get the formatted distance with unit. * * @param context the context - * @param distance_m the distance + * @param distance the distance * @param metricUnits true to use metric unit * @return the formatted distance (or null) and it's unit as {@link Pair} */ - public static Pair getDistanceParts(Context context, double distance_m, boolean metricUnits) { - if (Double.isNaN(distance_m) || Double.isInfinite(distance_m)) { + public static Pair getDistanceParts(Context context, Distance distance, boolean metricUnits) { + if (distance.isInvalid()) { return new Pair<>(null, context.getString(metricUnits ? R.string.unit_meter : R.string.unit_feet)); } - int unitId; if (metricUnits) { - if (distance_m > 500.0) { - distance_m *= UnitConversions.M_TO_KM; - unitId = R.string.unit_kilometer; + if (distance.greaterThan(Distance.of(500))) { + return new Pair<>(formatDecimal(distance.toKM()), context.getString(R.string.unit_kilometer)); } else { - unitId = R.string.unit_meter; + return new Pair<>(formatDecimal(distance.toM()), context.getString(R.string.unit_meter)); } } else { - if (distance_m * UnitConversions.M_TO_MI > 0.5) { - distance_m *= UnitConversions.M_TO_MI; - unitId = R.string.unit_mile; + if (distance.greaterThan(Distance.of(0.5 * UnitConversions.M_TO_MI))) { + return new Pair<>(formatDecimal(distance.toMI()), context.getString(R.string.unit_mile)); } else { - distance_m *= UnitConversions.M_TO_FT; - unitId = R.string.unit_feet; + return new Pair<>(formatDecimal(distance.toFT()), context.getString(R.string.unit_feet)); } } - return new Pair<>(formatDecimal(distance_m), context.getString(unitId)); } /** * Gets the formatted speed with unit. * * @param context the context - * @param speed_mps the speed + * @param speed the speed * @param metricUnits true to use metric unit * @param reportSpeed true to report speed; false for pace * @return the formatted speed (or null) and it's unit as {@link Pair} */ - public static Pair getSpeedParts(Context context, double speed_mps, boolean metricUnits, boolean reportSpeed) { + public static Pair getSpeedParts(Context context, Speed speed, boolean metricUnits, boolean reportSpeed) { int unitId; if (metricUnits) { unitId = reportSpeed ? R.string.unit_kilometer_per_hour : R.string.unit_minute_per_kilometer; @@ -214,21 +199,15 @@ public class StringUtils { } String unitString = context.getString(unitId); - - if (Double.isNaN(speed_mps) || Double.isInfinite(speed_mps)) { - speed_mps = 0; - } - - double speed = speed_mps * UnitConversions.M_TO_KM; //KM p sec - if (!metricUnits) { - speed *= UnitConversions.KM_TO_MI; + if (speed == null) { + speed = Speed.zero(); } if (reportSpeed) { - return new Pair<>(StringUtils.formatDecimal(speed * UnitConversions.S_TO_HR, 1), unitString); + return new Pair<>(StringUtils.formatDecimal(speed.to(metricUnits), 1), unitString); } - int pace = speed == 0 ? 0 : (int) Math.round(1 / speed); //sec / [KM | MI] + int pace = (int) speed.toPace(metricUnits).getSeconds(); int minutes = pace / 60; int seconds = pace % 60; diff --git a/src/main/java/de/dennisguse/opentracks/util/UnitConversions.java b/src/main/java/de/dennisguse/opentracks/util/UnitConversions.java index 8877d1402..91c0f6cde 100644 --- a/src/main/java/de/dennisguse/opentracks/util/UnitConversions.java +++ b/src/main/java/de/dennisguse/opentracks/util/UnitConversions.java @@ -49,7 +49,7 @@ public class UnitConversions { // Distance // multiplication factor to convert miles to feet - public static final double MI_TO_FT = 5280.0; + private static final double MI_TO_FT = 5280.0; // multiplication factor to convert feet to miles public static final double FT_TO_MI = 1 / MI_TO_FT; // multiplication factor to covert kilometers to meters diff --git a/src/main/java/de/dennisguse/opentracks/viewmodels/IntervalStatistics.java b/src/main/java/de/dennisguse/opentracks/viewmodels/IntervalStatistics.java index bc44d72f0..f009dddcd 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.distanceToPrevious(trackPoint); + interval.distance_m += prevTrackPoint.distanceToPrevious(trackPoint).toM(); interval.time = interval.time.plus(Duration.between(prevTrackPoint.getTime(), trackPoint.getTime())); interval.gain_m += trackPoint.hasAltitudeGain() ? trackPoint.getAltitudeGain() : 0; interval.loss_m += trackPoint.hasAltitudeLoss() ? trackPoint.getAltitudeLoss() : 0;