Value objects distance and speed.

#679
This commit is contained in:
Dennis Guse
2021-04-09 19:59:44 +02:00
parent f2b1d2a386
commit 6f9c788965
47 changed files with 773 additions and 502 deletions
@@ -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, "<br>");
descriptionGenerator.writeDistance(Distance.of(1100), builder, R.string.description_total_distance, "<br>");
assertEquals("Total distance: 1.10 km (0.7 mi)<br>", 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());
}
}
@@ -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<Marker> 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<TrackPoint> getTrackPoints(ContentProviderUtils contentProviderUtils, Track.Id trackId) {
@@ -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);
@@ -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());
}
@@ -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
@@ -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);
}
@@ -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;
}
}
@@ -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);
@@ -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();
@@ -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);
}
}
@@ -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")
@@ -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<TrackPoint> trackPoints = TestDataUtil.createTrack(new Track.Id(System.currentTimeMillis()), 10).second;
@@ -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));
}
}
@@ -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<Track> 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<Track> 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
@@ -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<TrackPoint> buildTrackPoints(int numberOfTrackPoints) {
return TestDataUtil.createTrack(new Track.Id(System.currentTimeMillis()), numberOfTrackPoints).second;
}
private List<TrackPoint> buildTrackPoints(int numberOfTrackPoints) {
return TestDataUtil.createTrack(new Track.Id(System.currentTimeMillis()), numberOfTrackPoints).second;
}
private TrackStatistics buildTrackStatistics(List<TrackPoint> trackPoints) {
private TrackStatistics buildTrackStatistics(List<TrackPoint> 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<TrackPoint> trackPoints = buildTrackPoints(50);
TrackStatistics trackStatistics = buildTrackStatistics(trackPoints);
float distanceInterval = 1000f;
// given
List<TrackPoint> 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<TrackPoint> trackPoints = buildTrackPoints(200);
TrackStatistics trackStatistics = buildTrackStatistics(trackPoints);
float distanceInterval = 1000f;
// given
List<TrackPoint> 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<TrackPoint> trackPoints = buildTrackPoints(200);
TrackStatistics trackStatistics = buildTrackStatistics(trackPoints);
float distanceInterval = 3000f;
// given
List<TrackPoint> 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<TrackPoint> trackPoints = buildTrackPoints(1000);
TrackStatistics trackStatistics = buildTrackStatistics(trackPoints);
float distanceInterval = 3000f;
// given
List<TrackPoint> 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<TrackPoint> trackPoints = buildTrackPoints(10000);
TrackStatistics trackStatistics = buildTrackStatistics(trackPoints);
float distanceInterval = 1000f;
// given
List<TrackPoint> 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<TrackPoint> trackPoints, TrackStatistics trackStatistics, float distanceInterval) {
IntervalStatistics intervalStatistics = new IntervalStatistics(trackPoints, distanceInterval);
List<IntervalStatistics.Interval> 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<TrackPoint> trackPoints, TrackStatistics trackStatistics, float distanceInterval) {
IntervalStatistics intervalStatistics = new IntervalStatistics(trackPoints, distanceInterval);
List<IntervalStatistics.Interval> 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);
}
}
@@ -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);
@@ -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<RecyclerView
} else {
sumDistance_m = nextPosition * interval.getDistance_m();
}
viewHolder.distance.setText(StringUtils.formatDistance(context, sumDistance_m, metricUnits));
viewHolder.distance.setText(StringUtils.formatDistance(context, Distance.of(sumDistance_m), metricUnits));
viewHolder.rate.setText(StringUtils.formatSpeed(context, interval.getSpeed_ms(), metricUnits, isReportSpeed));
viewHolder.rate.setText(StringUtils.formatSpeed(context, Speed.of(interval.getSpeed_ms()), metricUnits, isReportSpeed));
viewHolder.gain.setText(StringUtils.formatDistance(context, interval.getGain_m(), metricUnits));
viewHolder.loss.setText(StringUtils.formatDistance(context, interval.getLoss_m(), metricUnits));
viewHolder.gain.setText(StringUtils.formatDistance(context, Distance.of(interval.getGain_m()), metricUnits));
viewHolder.loss.setText(StringUtils.formatDistance(context, Distance.of(interval.getLoss_m()), metricUnits));
}
@Override
@@ -29,11 +29,7 @@ public class ChartPoint {
TrackStatistics trackStatistics = trackStatisticsUpdater.getTrackStatistics();
if (chartByDistance) {
double distance = trackStatistics.getTotalDistance() * UnitConversions.M_TO_KM;
if (!metricUnits) {
distance *= UnitConversions.KM_TO_MI;
}
timeOrDistance = distance;
timeOrDistance = trackStatistics.getTotalDistance().to(metricUnits);
} else {
timeOrDistance = trackStatistics.getTotalTime().toMillis();
}
@@ -43,11 +39,8 @@ public class ChartPoint {
altitude *= UnitConversions.M_TO_FT;
}
speed = trackStatisticsUpdater.getSmoothedSpeed() * UnitConversions.MPS_TO_KMH;
if (!metricUnits) {
speed *= UnitConversions.KM_TO_MI;
}
pace = speed == 0 ? 0.0 : 60.0 / speed;
speed = trackStatisticsUpdater.getSmoothedSpeed().to(metricUnits);
pace = trackStatisticsUpdater.getSmoothedSpeed().toPace(metricUnits).toMillis() * UnitConversions.MS_TO_S * UnitConversions.S_TO_MIN;
if (trackPoint != null) {
if (trackPoint.hasHeartRate()) {
heartRate = trackPoint.getHeartRate_bpm();
@@ -51,7 +51,6 @@ import de.dennisguse.opentracks.util.IntentUtils;
import de.dennisguse.opentracks.util.MarkerUtils;
import de.dennisguse.opentracks.util.StringUtils;
import de.dennisguse.opentracks.util.ThemeUtils;
import de.dennisguse.opentracks.util.UnitConversions;
/**
* Visualization of the chart.
@@ -994,8 +993,7 @@ public class ChartView extends View {
private double getMarkerXValue(Marker marker) {
if (chartByDistance) {
double length_km = marker.getLength() * UnitConversions.M_TO_KM;
return metricUnits ? length_km : length_km * UnitConversions.KM_TO_MI;
return marker.getLength().to(metricUnits);
} else {
return marker.getDuration().toMillis();
}
@@ -24,6 +24,8 @@ import androidx.annotation.VisibleForTesting;
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.content.data.Track;
import de.dennisguse.opentracks.stats.TrackStatistics;
import de.dennisguse.opentracks.util.StringUtils;
@@ -163,9 +165,9 @@ public class DescriptionGenerator {
* @param lineBreak line break string
*/
@VisibleForTesting
void writeDistance(double distance, StringBuilder builder, int resId, String lineBreak) {
double distanceInKm = distance * UnitConversions.M_TO_KM;
double distanceInMi = distanceInKm * UnitConversions.KM_TO_MI;
void writeDistance(Distance distance, StringBuilder builder, int resId, String lineBreak) {
double distanceInKm = distance.toKM();
double distanceInMi = distance.toMI();
builder.append(context.getString(resId, distanceInKm, distanceInMi));
builder.append(lineBreak);
}
@@ -193,21 +195,18 @@ public class DescriptionGenerator {
* @param lineBreak line break string
*/
@VisibleForTesting
void writeSpeed(double speed, StringBuilder builder, int resId, String lineBreak) {
double speedInKmHr = speed * UnitConversions.MPS_TO_KMH;
double speedInMiHr = speedInKmHr * UnitConversions.KM_TO_MI;
builder.append(context.getString(resId, speedInKmHr, speedInMiHr));
void writeSpeed(Speed speed, StringBuilder builder, int resId, String lineBreak) {
builder.append(context.getString(resId, speed.toKMH(), speed.toMPH()));
builder.append(lineBreak);
}
/**
* @param speed speed in meters per second
* @param builder StringBuilder to append pace
* @param resId resource id of pace string
* @param lineBreak line break string
*/
@VisibleForTesting
void writePace(double speed, StringBuilder builder, int resId, String lineBreak) {
void writePace(Speed speed, StringBuilder builder, int resId, String lineBreak) {
Pair<String, String> paceInMetrics = StringUtils.getSpeedParts(context, speed, true, false);
Pair<String, String> paceInImperial = StringUtils.getSpeedParts(context, speed, false, false);
@@ -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 +
'}';
}
}
@@ -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;
}
@@ -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 +
'}';
}
}
@@ -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;
}
@@ -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());
@@ -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 +
'}';
}
}
@@ -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()) {
@@ -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;
}
}
@@ -180,8 +180,7 @@ public class StatisticsRecordedFragment extends Fragment {
private void updateUI() {
// Set total distance
{
double totalDistance = trackStatistics.getTotalDistance();
Pair<String, String> parts = StringUtils.getDistanceParts(getContext(), totalDistance, preferenceMetricUnits);
Pair<String, String> 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<String, String> parts = StringUtils.getSpeedParts(getContext(), speed, preferenceMetricUnits, preferenceReportSpeed);
Pair<String, String> 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<String, String> parts = StringUtils.getSpeedParts(getContext(), speed, preferenceMetricUnits, preferenceReportSpeed);
Pair<String, String> 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<String, String> parts = StringUtils.getSpeedParts(getContext(), speed, preferenceMetricUnits, preferenceReportSpeed);
Pair<String, String> parts = StringUtils.getSpeedParts(getContext(), trackStatistics.getAverageMovingSpeed(), preferenceMetricUnits, preferenceReportSpeed);
viewBinding.statsMovingSpeedValue.setText(parts.first);
viewBinding.statsMovingSpeedUnit.setText(parts.second);
}
@@ -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<String, String> 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<String, String> 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<String, String> parts = StringUtils.getSpeedParts(getContext(), speed, preferenceMetricUnits, preferenceReportSpeed);
@@ -290,7 +290,7 @@ public class GPXTrackExporter implements TrackExporter {
printWriter.println("<extensions><gpxtpx:TrackPointExtension>");
if (trackPoint.hasSpeed()) {
printWriter.println("<gpxtpx:speed>" + SPEED_FORMAT.format(trackPoint.getSpeed()) + "</gpxtpx:speed>");
printWriter.println("<gpxtpx:speed>" + SPEED_FORMAT.format(trackPoint.getSpeed().toMPS()) + "</gpxtpx:speed>");
}
if (trackPoint.hasHeartRate()) {
@@ -395,10 +395,10 @@ public class KMLTrackExporter implements TrackExporter {
} else {
printWriter.println("<gx:coord/>");
}
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);
@@ -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<Track.Id> trackIds = new ArrayList<>();
private final List<Marker> 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);
}
@@ -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));
@@ -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);
@@ -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;
@@ -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;
@@ -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?
}
}
}
@@ -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);
@@ -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]);
@@ -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() {
@@ -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;
@@ -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;
@@ -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
@@ -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<String, String> getDistanceParts(Context context, double distance_m, boolean metricUnits) {
if (Double.isNaN(distance_m) || Double.isInfinite(distance_m)) {
public static Pair<String, String> 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<String, String> getSpeedParts(Context context, double speed_mps, boolean metricUnits, boolean reportSpeed) {
public static Pair<String, String> 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;
@@ -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
@@ -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;