forked from upstream-mirrors/OpenTracks
Merge branch 'barometer#10' into main
This commit is contained in:
@@ -68,9 +68,9 @@ public class TestDataUtil {
|
|||||||
trackPoint.setSpeed(5f + (i / 10));
|
trackPoint.setSpeed(5f + (i / 10));
|
||||||
|
|
||||||
trackPoint.setHeartRate_bpm(100f + i);
|
trackPoint.setHeartRate_bpm(100f + i);
|
||||||
trackPoint.setCyclingCadence_rpm(200f + i);
|
|
||||||
trackPoint.setCyclingCadence_rpm(300f + i);
|
trackPoint.setCyclingCadence_rpm(300f + i);
|
||||||
trackPoint.setPower(400f + i);
|
trackPoint.setPower(400f + i);
|
||||||
|
trackPoint.setElevationGain(500f + i);
|
||||||
return trackPoint;
|
return trackPoint;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -455,7 +455,7 @@ public class CustomContentProviderUtilsTest {
|
|||||||
statistics.setTotalTime(1500L);
|
statistics.setTotalTime(1500L);
|
||||||
statistics.setMovingTime(700L);
|
statistics.setMovingTime(700L);
|
||||||
statistics.setTotalDistance(750.0);
|
statistics.setTotalDistance(750.0);
|
||||||
statistics.setTotalElevationGain(50.0);
|
statistics.setTotalElevationGain(50.0f);
|
||||||
statistics.setMaxSpeed(60.0);
|
statistics.setMaxSpeed(60.0);
|
||||||
statistics.setMaxElevation(1250.0);
|
statistics.setMaxElevation(1250.0);
|
||||||
statistics.setMinElevation(1200.0);
|
statistics.setMinElevation(1200.0);
|
||||||
|
|||||||
+7
-4
@@ -133,7 +133,7 @@ public class ExportImportTest {
|
|||||||
assertMarkers();
|
assertMarkers();
|
||||||
|
|
||||||
// 3. trackpoints
|
// 3. trackpoints
|
||||||
assertTrackpoints(false, false, false);
|
assertTrackpoints(false, false, false, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@LargeTest
|
@LargeTest
|
||||||
@@ -168,7 +168,7 @@ public class ExportImportTest {
|
|||||||
assertMarkers();
|
assertMarkers();
|
||||||
|
|
||||||
// 3. trackpoints
|
// 3. trackpoints
|
||||||
assertTrackpoints(true, true, true);
|
assertTrackpoints(true, true, true, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@LargeTest
|
@LargeTest
|
||||||
@@ -257,7 +257,7 @@ public class ExportImportTest {
|
|||||||
assertMarkers();
|
assertMarkers();
|
||||||
|
|
||||||
// 3. trackpoints
|
// 3. trackpoints
|
||||||
assertTrackpoints(false, true, true);
|
assertTrackpoints(false, true, true, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@LargeTest
|
@LargeTest
|
||||||
@@ -304,7 +304,7 @@ public class ExportImportTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assertTrackpoints(boolean verifyPower, boolean verifyHeartrate, boolean verifyCadence) {
|
private void assertTrackpoints(boolean verifyPower, boolean verifyHeartrate, boolean verifyCadence, boolean verifyElevationGain) {
|
||||||
List<TrackPoint> importedTrackPoints = contentProviderUtils.getTrackPoints(importTrackId);
|
List<TrackPoint> importedTrackPoints = contentProviderUtils.getTrackPoints(importTrackId);
|
||||||
assertEquals(trackPoints.size(), importedTrackPoints.size());
|
assertEquals(trackPoints.size(), importedTrackPoints.size());
|
||||||
|
|
||||||
@@ -330,6 +330,9 @@ public class ExportImportTest {
|
|||||||
if (verifyPower) {
|
if (verifyPower) {
|
||||||
assertEquals(trackPoint.getPower(), importedTrackPoint.getPower(), 0.01);
|
assertEquals(trackPoint.getPower(), importedTrackPoint.getPower(), 0.01);
|
||||||
}
|
}
|
||||||
|
if (verifyElevationGain) {
|
||||||
|
assertEquals(trackPoint.getElevationGain(), importedTrackPoint.getElevationGain(), 0.01);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+50
@@ -0,0 +1,50 @@
|
|||||||
|
package de.dennisguse.opentracks.services.sensors;
|
||||||
|
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* NOTE: Test data is completely artificial.
|
||||||
|
*/
|
||||||
|
public class ElevationSumManagerTest {
|
||||||
|
|
||||||
|
private ElevationSumManager elevationSumManager = new ElevationSumManager();
|
||||||
|
|
||||||
|
private static void addSensorValue(ElevationSumManager elevationSumManager, float[] values) {
|
||||||
|
for (float f : values) {
|
||||||
|
elevationSumManager.onSensorValueChanged(f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
elevationSumManager.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getElevationGainLoss_downhill() {
|
||||||
|
// given
|
||||||
|
elevationSumManager.setConnected(true);
|
||||||
|
|
||||||
|
// then
|
||||||
|
addSensorValue(elevationSumManager, new float[]{1015f, 1015.01f, 1015.02f, 1015.03f, 1015.04f, 1015.05f, 1015.06f, 1015.07f, 1015.08f, 1015.09f, 1015.10f, 1015.11f, 1015.12f, 1015.13f, 1015.14f, 1015.15f});
|
||||||
|
|
||||||
|
// then
|
||||||
|
Assert.assertEquals(0f, elevationSumManager.getElevationGain_m(), 0.01);
|
||||||
|
Assert.assertEquals(-48.0, elevationSumManager.getElevationLoss_m(), 0.01);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void sensorUnavailable() {
|
||||||
|
// given
|
||||||
|
elevationSumManager.setConnected(false);
|
||||||
|
|
||||||
|
// then
|
||||||
|
elevationSumManager.onSensorValueChanged(999f);
|
||||||
|
|
||||||
|
// then
|
||||||
|
Assert.assertNull(elevationSumManager.getElevationGain_m());
|
||||||
|
Assert.assertNull(elevationSumManager.getElevationLoss_m());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -51,8 +51,8 @@ public class TrackStatisticsTest {
|
|||||||
statistics2.setMovingTime(600L); // Result: 700+600
|
statistics2.setMovingTime(600L); // Result: 700+600
|
||||||
statistics.setTotalDistance(750.0);
|
statistics.setTotalDistance(750.0);
|
||||||
statistics2.setTotalDistance(350.0); // Result: 750+350
|
statistics2.setTotalDistance(350.0); // Result: 750+350
|
||||||
statistics.setTotalElevationGain(50.0);
|
statistics.setTotalElevationGain(50.0f);
|
||||||
statistics2.setTotalElevationGain(850.0); // Result: 850+50
|
statistics2.setTotalElevationGain(850.0f); // Result: 850+50
|
||||||
statistics.setMaxSpeed(60.0); // Resulting max speed
|
statistics.setMaxSpeed(60.0); // Resulting max speed
|
||||||
statistics2.setMaxSpeed(30.0);
|
statistics2.setMaxSpeed(30.0);
|
||||||
statistics.setMaxElevation(1250.0);
|
statistics.setMaxElevation(1250.0);
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
package de.dennisguse.opentracks.util;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertNull;
|
||||||
|
|
||||||
|
public class PressureSensorUtilsTest {
|
||||||
|
|
||||||
|
// real data at 500Hz stationary
|
||||||
|
@Test
|
||||||
|
public void elevationChanges_none() {
|
||||||
|
// given
|
||||||
|
float[] sensorValues_hPa = new float[]{1015.6876f, 1015.699f, 1015.70905f, 1015.71075f, 1015.7224f, 1015.72f, 1015.7373f, 1015.7481f, 1015.74133f, 1015.73553f, 1015.7462f, 1015.74896f, 1015.752f, 1015.754f, 1015.7456f, 1015.73303f, 1015.7288f, 1015.7239f, 1015.7298f, 1015.7387f, 1015.72534f, 1015.72577f, 1015.72797f, 1015.71796f, 1015.7113f, 1015.7097f, 1015.7096f, 1015.7013f, 1015.7072f, 1015.71063f, 1015.7088f, 1015.7115f, 1015.713f, 1015.7121f, 1015.7062f, 1015.7062f, 1015.6994f, 1015.69617f, 1015.68945f, 1015.698f, 1015.6913f, 1015.69696f, 1015.69293f, 1015.6955f, 1015.6928f, 1015.6939f, 1015.68787f, 1015.6881f, 1015.68536f, 1015.68726f, 1015.6879f, 1015.6904f, 1015.6937f, 1015.69476f, 1015.7046f, 1015.7019f, 1015.7079f, 1015.71124f, 1015.71216f, 1015.71436f, 1015.7171f, 1015.7186f, 1015.7186f, 1015.7246f, 1015.72046f, 1015.7211f, 1015.72375f, 1015.7194f, 1015.72125f, 1015.7161f, 1015.71185f, 1015.7188f, 1015.721f, 1015.7252f, 1015.72266f, 1015.7236f, 1015.72614f, 1015.7266f, 1015.7344f, 1015.7376f, 1015.7337f, 1015.7253f, 1015.71875f, 1015.7219f, 1015.7219f, 1015.7317f, 1015.7284f, 1015.73444f, 1015.73584f, 1015.7369f, 1015.73254f, 1015.7369f, 1015.7336f, 1015.73254f, 1015.73676f, 1015.74176f, 1015.74506f, 1015.7478f, 1015.755f, 1015.7515f, 1015.7543f, 1015.7459f, 1015.7468f, 1015.7492f, 1015.74585f, 1015.74756f, 1015.74756f, 1015.75085f, 1015.754f, 1015.75336f, 1015.7568f, 1015.75165f, 1015.761f, 1015.7527f, 1015.75684f, 1015.7635f, 1015.7585f, 1015.7552f, 1015.7475f, 1015.74506f, 1015.7542f, 1015.75586f, 1015.74176f, 1015.74414f, 1015.7501f, 1015.751f, 1015.7478f, 1015.755f, 1015.75836f, 1015.7592f, 1015.75256f, 1015.7559f, 1015.76654f, 1015.76154f, 1015.766f, 1015.77014f, 1015.76904f, 1015.76013f, 1015.7674f, 1015.76514f, 1015.7674f, 1015.7635f, 1015.76404f, 1015.7607f, 1015.754f, 1015.7574f, 1015.7607f, 1015.7607f, 1015.7607f, 1015.754f, 1015.7567f, 1015.7616f, 1015.7643f, 1015.7666f, 1015.7609f, 1015.766f, 1015.76764f, 1015.755f, 1015.75757f, 1015.74927f, 1015.74927f, 1015.7426f, 1015.745f, 1015.745f, 1015.7409f, 1015.7376f, 1015.7326f, 1015.74164f, 1015.74664f, 1015.745f, 1015.7459f, 1015.7502f, 1015.7493f, 1015.7476f, 1015.7493f, 1015.751f, 1015.7601f, 1015.76434f, 1015.766f, 1015.756f, 1015.7617f, 1015.7623f, 1015.76404f, 1015.76733f, 1015.7607f, 1015.7623f, 1015.76337f, 1015.7584f, 1015.7573f, 1015.7502f, 1015.7534f, 1015.7534f, 1015.7601f, 1015.75684f, 1015.7518f, 1015.75574f, 1015.7524f, 1015.7507f, 1015.7484f, 1015.7502f, 1015.7534f, 1015.7623f, 1015.75903f, 1015.75574f, 1015.75574f, 1015.7573f, 1015.7524f, 1015.75574f, 1015.75574f, 1015.7607f, 1015.7573f, 1015.754f, 1015.74677f, 1015.754f, 1015.75574f, 1015.75903f, 1015.7573f, 1015.7551f, 1015.7573f, 1015.754f, 1015.7507f, 1015.7374f, 1015.739f, 1015.73566f, 1015.73566f, 1015.7367f, 1015.7417f, 1015.7417f, 1015.739f, 1015.74835f, 1015.74567f, 1015.7517f, 1015.74066f, 1015.7374f, 1015.739f, 1015.7417f, 1015.73065f, 1015.72894f, 1015.73505f, 1015.73334f, 1015.74005f, 1015.74005f, 1015.7367f, 1015.7434f, 1015.744f, 1015.7423f, 1015.7384f, 1015.739f, 1015.7374f, 1015.7374f, 1015.7374f, 1015.7341f, 1015.73505f, 1015.7341f, 1015.7267f, 1015.72833f, 1015.7234f, 1015.7167f, 1015.725f, 1015.7217f, 1015.7234f, 1015.73004f, 1015.7324f, 1015.7423f, 1015.74005f, 1015.73505f, 1015.73004f, 1015.739f, 1015.7417f, 1015.74567f, 1015.74066f, 1015.74146f, 1015.74146f, 1015.73816f, 1015.7274f, 1015.72906f, 1015.7274f, 1015.7341f, 1015.739f, 1015.744f, 1015.7417f, 1015.7374f, 1015.7324f, 1015.73816f, 1015.74146f, 1015.7374f, 1015.7341f, 1015.7365f, 1015.7365f, 1015.74146f, 1015.74414f, 1015.7475f, 1015.7448f, 1015.74146f, 1015.74585f, 1015.7475f, 1015.75146f, 1015.75146f, 1015.7591f, 1015.75476f, 1015.7614f, 1015.75476f, 1015.7492f, 1015.74817f, 1015.7498f, 1015.7515f, 1015.75586f, 1015.7509f, 1015.7448f, 1015.7525f, 1015.7581f, 1015.7592f};
|
||||||
|
float firstSensorValue = sensorValues_hPa[0];
|
||||||
|
|
||||||
|
// when // then
|
||||||
|
for (float v : sensorValues_hPa) {
|
||||||
|
assertNull(PressureSensorUtils.computeChanges_m(firstSensorValue, v));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Simulate a sudden drop of elevation.
|
||||||
|
// test data
|
||||||
|
@Test
|
||||||
|
public void computeChanges_downhill() {
|
||||||
|
float[] sensorValues_hPa = new float[]{1015f, 1015.01f, 1015.02f, 1015.03f, 1015.04f, 1015.05f, 1015.06f, 1015.07f, 1015.08f, 1015.09f, 1015.10f, 1015.11f, 1015.12f, 1015.13f, 1018f, 1018.1f, 1018.1f, 1018.1f, 1018.1f};
|
||||||
|
float firstSensorValue = sensorValues_hPa[0];
|
||||||
|
|
||||||
|
// when
|
||||||
|
float elevationGain_m = 0;
|
||||||
|
float elevationLoss_m = 0;
|
||||||
|
float lastUsedPressureValue_hPa = firstSensorValue;
|
||||||
|
|
||||||
|
for (float v : sensorValues_hPa) {
|
||||||
|
PressureSensorUtils.ElevationChange elevationChange = PressureSensorUtils.computeChanges_m(lastUsedPressureValue_hPa, v);
|
||||||
|
if (elevationChange != null) {
|
||||||
|
elevationGain_m += elevationChange.getElevationGain_m();
|
||||||
|
elevationLoss_m += elevationChange.getElevationLoss_m();
|
||||||
|
lastUsedPressureValue_hPa = elevationChange.getCurrentSensorValue_hPa();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertEquals(0f, elevationGain_m, 0.01);
|
||||||
|
assertEquals(-15.0f, elevationLoss_m, 0.01);
|
||||||
|
assertEquals(1016.80f, lastUsedPressureValue_hPa, 0.01); //Expect exponential smoothing
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
-1
@@ -35,7 +35,7 @@ public class AggregatedStatisticsTest {
|
|||||||
statistics.setTotalTime(totalTime);
|
statistics.setTotalTime(totalTime);
|
||||||
statistics.setMovingTime(totalTime);
|
statistics.setMovingTime(totalTime);
|
||||||
statistics.setTotalDistance(totalDistance);
|
statistics.setTotalDistance(totalDistance);
|
||||||
statistics.setTotalElevationGain(50.0);
|
statistics.setTotalElevationGain(50.0f);
|
||||||
statistics.setMaxSpeed(50.0); // Resulting max speed
|
statistics.setMaxSpeed(50.0); // Resulting max speed
|
||||||
statistics.setMaxElevation(1250.0);
|
statistics.setMaxElevation(1250.0);
|
||||||
statistics.setMinElevation(1200.0); // Resulting min elevation
|
statistics.setMinElevation(1200.0); // Resulting min elevation
|
||||||
|
|||||||
@@ -136,7 +136,9 @@ public class DescriptionGenerator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Elevation gain
|
// Elevation gain
|
||||||
writeElevation(stats.getTotalElevationGain(), builder, R.string.description_elevation_gain, lineBreak);
|
if (stats.hasTotalElevationGain()) {
|
||||||
|
writeElevation(stats.getTotalElevationGain(), builder, R.string.description_elevation_gain, lineBreak);
|
||||||
|
}
|
||||||
|
|
||||||
// Recorded time
|
// Recorded time
|
||||||
builder.append(context.getString(R.string.description_recorded_time, StringUtils.formatDateTime(context, stats.getStartTime_ms())));
|
builder.append(context.getString(R.string.description_recorded_time, StringUtils.formatDateTime(context, stats.getStartTime_ms())));
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ public class TrackPoint {
|
|||||||
private Float heartRate_bpm = null;
|
private Float heartRate_bpm = null;
|
||||||
private Float cyclingCadence_rpm = null;
|
private Float cyclingCadence_rpm = null;
|
||||||
private Float power = null;
|
private Float power = null;
|
||||||
|
private Float elevationGain = null;
|
||||||
|
|
||||||
public TrackPoint() {
|
public TrackPoint() {
|
||||||
this.location = new Location("");
|
this.location = new Location("");
|
||||||
@@ -92,6 +93,18 @@ public class TrackPoint {
|
|||||||
return location;
|
return location;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean hasElevationGain() {
|
||||||
|
return elevationGain != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getElevationGain() {
|
||||||
|
return elevationGain;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setElevationGain(Float elevationGain) {
|
||||||
|
this.elevationGain = elevationGain;
|
||||||
|
}
|
||||||
|
|
||||||
public double getLatitude() {
|
public double getLatitude() {
|
||||||
return location.getLatitude();
|
return location.getLatitude();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ public interface TrackPointsColumns extends BaseColumns {
|
|||||||
String SENSOR_HEARTRATE = "sensor_heartrate";
|
String SENSOR_HEARTRATE = "sensor_heartrate";
|
||||||
String SENSOR_CADENCE = "sensor_cadence";
|
String SENSOR_CADENCE = "sensor_cadence";
|
||||||
String SENSOR_POWER = "sensor_power";
|
String SENSOR_POWER = "sensor_power";
|
||||||
|
String ELEVATION_GAIN = "elevation_gain";
|
||||||
|
|
||||||
String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " ("
|
String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " ("
|
||||||
+ _ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
|
+ _ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
|
||||||
@@ -64,7 +65,8 @@ public interface TrackPointsColumns extends BaseColumns {
|
|||||||
+ BEARING + " FLOAT, "
|
+ BEARING + " FLOAT, "
|
||||||
+ SENSOR_HEARTRATE + " FLOAT, "
|
+ SENSOR_HEARTRATE + " FLOAT, "
|
||||||
+ SENSOR_CADENCE + " FLOAT, "
|
+ SENSOR_CADENCE + " FLOAT, "
|
||||||
+ SENSOR_POWER + " FLOAT)";
|
+ SENSOR_POWER + " FLOAT, "
|
||||||
|
+ ELEVATION_GAIN + " FLOAT)";
|
||||||
|
|
||||||
String CREATE_TABLE_INDEX = "CREATE INDEX " + TABLE_NAME + "_" + TRACKID + "_index ON " + TABLE_NAME + "(" + TRACKID + ")";
|
String CREATE_TABLE_INDEX = "CREATE INDEX " + TABLE_NAME + "_" + TRACKID + "_index ON " + TABLE_NAME + "(" + TRACKID + ")";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ class CachedTrackPointsIndexes {
|
|||||||
final int sensorHeartRateIndex;
|
final int sensorHeartRateIndex;
|
||||||
final int sensorCadenceIndex;
|
final int sensorCadenceIndex;
|
||||||
final int sensorPowerIndex;
|
final int sensorPowerIndex;
|
||||||
|
final int elevationGainIndex;
|
||||||
|
|
||||||
CachedTrackPointsIndexes(Cursor cursor) {
|
CachedTrackPointsIndexes(Cursor cursor) {
|
||||||
idIndex = cursor.getColumnIndex(TrackPointsColumns._ID);
|
idIndex = cursor.getColumnIndex(TrackPointsColumns._ID);
|
||||||
@@ -32,5 +33,6 @@ class CachedTrackPointsIndexes {
|
|||||||
sensorHeartRateIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.SENSOR_HEARTRATE);
|
sensorHeartRateIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.SENSOR_HEARTRATE);
|
||||||
sensorCadenceIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.SENSOR_CADENCE);
|
sensorCadenceIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.SENSOR_CADENCE);
|
||||||
sensorPowerIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.SENSOR_POWER);
|
sensorPowerIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.SENSOR_POWER);
|
||||||
|
elevationGainIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.ELEVATION_GAIN);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -648,6 +648,10 @@ public class ContentProviderUtils {
|
|||||||
trackPoint.setPower(cursor.getFloat(indexes.sensorPowerIndex));
|
trackPoint.setPower(cursor.getFloat(indexes.sensorPowerIndex));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!cursor.isNull(indexes.elevationGainIndex)) {
|
||||||
|
trackPoint.setElevationGain(cursor.getFloat(indexes.elevationGainIndex));
|
||||||
|
}
|
||||||
|
|
||||||
return trackPoint;
|
return trackPoint;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -836,6 +840,11 @@ public class ContentProviderUtils {
|
|||||||
if (trackPoint.hasPower()) {
|
if (trackPoint.hasPower()) {
|
||||||
values.put(TrackPointsColumns.SENSOR_POWER, trackPoint.getPower());
|
values.put(TrackPointsColumns.SENSOR_POWER, trackPoint.getPower());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (trackPoint.hasElevationGain()) {
|
||||||
|
values.put(TrackPointsColumns.ELEVATION_GAIN, trackPoint.getElevationGain());
|
||||||
|
}
|
||||||
|
|
||||||
return values;
|
return values;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+31
-1
@@ -25,7 +25,7 @@ public class CustomSQLiteOpenHelper extends SQLiteOpenHelper {
|
|||||||
|
|
||||||
private static final String TAG = CustomSQLiteOpenHelper.class.getSimpleName();
|
private static final String TAG = CustomSQLiteOpenHelper.class.getSimpleName();
|
||||||
|
|
||||||
private static final int DATABASE_VERSION = 26;
|
private static final int DATABASE_VERSION = 27;
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
public static final String DATABASE_NAME = "database.db";
|
public static final String DATABASE_NAME = "database.db";
|
||||||
@@ -70,6 +70,10 @@ public class CustomSQLiteOpenHelper extends SQLiteOpenHelper {
|
|||||||
case 26:
|
case 26:
|
||||||
upgradeFrom25to26(db);
|
upgradeFrom25to26(db);
|
||||||
break;
|
break;
|
||||||
|
case 27:
|
||||||
|
upgradeFrom26to27(db);
|
||||||
|
break;
|
||||||
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new RuntimeException("Not implemented: upgrade to " + toVersion);
|
throw new RuntimeException("Not implemented: upgrade to " + toVersion);
|
||||||
@@ -91,6 +95,9 @@ public class CustomSQLiteOpenHelper extends SQLiteOpenHelper {
|
|||||||
case 25:
|
case 25:
|
||||||
downgradeFrom26to25(db);
|
downgradeFrom26to25(db);
|
||||||
break;
|
break;
|
||||||
|
case 26:
|
||||||
|
downgradeFrom27to26(db);
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new RuntimeException("Not implemented: downgrade to " + toVersion);
|
throw new RuntimeException("Not implemented: downgrade to " + toVersion);
|
||||||
@@ -190,4 +197,27 @@ public class CustomSQLiteOpenHelper extends SQLiteOpenHelper {
|
|||||||
db.setTransactionSuccessful();
|
db.setTransactionSuccessful();
|
||||||
db.endTransaction();
|
db.endTransaction();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void upgradeFrom26to27(SQLiteDatabase db) {
|
||||||
|
db.beginTransaction();
|
||||||
|
|
||||||
|
db.execSQL("ALTER TABLE trackpoints ADD COLUMN elevation_gain FLOAT");
|
||||||
|
|
||||||
|
db.setTransactionSuccessful();
|
||||||
|
db.endTransaction();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void downgradeFrom27to26(SQLiteDatabase db) {
|
||||||
|
db.beginTransaction();
|
||||||
|
|
||||||
|
db.execSQL("ALTER TABLE trackpoints RENAME TO trackpoints_old");
|
||||||
|
db.execSQL("CREATE TABLE trackpoints (_id INTEGER PRIMARY KEY AUTOINCREMENT, trackid INTEGER, longitude INTEGER, latitude INTEGER, time INTEGER, elevation FLOAT, accuracy FLOAT, speed FLOAT, bearing FLOAT, sensor_heartrate FLOAT, sensor_cadence FLOAT, sensor_power FLOAT)");
|
||||||
|
db.execSQL("INSERT INTO trackpoints SELECT _id, trackid, longitude, latitude, time, elevation, accuracy, speed, bearing, sensor_heartrate, sensor_cadence, sensor_power FROM trackpoints_old");
|
||||||
|
db.execSQL("DROP TABLE trackpoints_old");
|
||||||
|
|
||||||
|
db.execSQL("CREATE INDEX trackpoints_trackid_index ON trackpoints(trackid)");
|
||||||
|
|
||||||
|
db.setTransactionSuccessful();
|
||||||
|
db.endTransaction();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import android.widget.TextView;
|
|||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.constraintlayout.widget.Group;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.R;
|
import de.dennisguse.opentracks.R;
|
||||||
@@ -87,6 +88,10 @@ public class StatisticsRecordedFragment extends Fragment {
|
|||||||
private TextView speedMovingValue;
|
private TextView speedMovingValue;
|
||||||
private TextView speedMovingUnit;
|
private TextView speedMovingUnit;
|
||||||
|
|
||||||
|
private Group elevationGroup;
|
||||||
|
private TextView elevationTotalGainValue;
|
||||||
|
private TextView elevationTotalGainUnit;
|
||||||
|
|
||||||
public static StatisticsRecordedFragment newInstance(Track.Id trackId) {
|
public static StatisticsRecordedFragment newInstance(Track.Id trackId) {
|
||||||
Bundle bundle = new Bundle();
|
Bundle bundle = new Bundle();
|
||||||
bundle.putParcelable(TRACK_ID_KEY, trackId);
|
bundle.putParcelable(TRACK_ID_KEY, trackId);
|
||||||
@@ -130,6 +135,10 @@ public class StatisticsRecordedFragment extends Fragment {
|
|||||||
speedMovingLabel = view.findViewById(R.id.stats_moving_speed_label);
|
speedMovingLabel = view.findViewById(R.id.stats_moving_speed_label);
|
||||||
speedMovingValue = view.findViewById(R.id.stats_moving_speed_value);
|
speedMovingValue = view.findViewById(R.id.stats_moving_speed_value);
|
||||||
speedMovingUnit = view.findViewById(R.id.stats_moving_speed_unit);
|
speedMovingUnit = view.findViewById(R.id.stats_moving_speed_unit);
|
||||||
|
|
||||||
|
elevationGroup = view.findViewById(R.id.stats_elevation_group);
|
||||||
|
elevationTotalGainValue = view.findViewById(R.id.stats_elevation_gain_value);
|
||||||
|
elevationTotalGainUnit = view.findViewById(R.id.stats_elevation_gain_unit);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -187,6 +196,10 @@ public class StatisticsRecordedFragment extends Fragment {
|
|||||||
speedMovingLabel = null;
|
speedMovingLabel = null;
|
||||||
speedMovingValue = null;
|
speedMovingValue = null;
|
||||||
speedMovingUnit = null;
|
speedMovingUnit = null;
|
||||||
|
|
||||||
|
elevationGroup = null;
|
||||||
|
elevationTotalGainValue = null;
|
||||||
|
elevationTotalGainUnit = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void loadStatistics() {
|
public void loadStatistics() {
|
||||||
@@ -264,5 +277,18 @@ public class StatisticsRecordedFragment extends Fragment {
|
|||||||
speedMovingValue.setText(parts.first);
|
speedMovingValue.setText(parts.first);
|
||||||
speedMovingUnit.setText(parts.second);
|
speedMovingUnit.setText(parts.second);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set elevation gain
|
||||||
|
{
|
||||||
|
// Make elevation visible?
|
||||||
|
boolean showElevation = PreferencesUtils.isShowStatsElevation(getContext());
|
||||||
|
elevationGroup.setVisibility(showElevation ? View.VISIBLE : View.GONE);
|
||||||
|
|
||||||
|
Float elevationGain_m = trackStatistics != null ? trackStatistics.getTotalElevationGain() : null;
|
||||||
|
|
||||||
|
Pair<String, String> parts = StringUtils.formatElevation(getContext(), elevationGain_m, metricUnits);
|
||||||
|
elevationTotalGainValue.setText(parts.first);
|
||||||
|
elevationTotalGainUnit.setText(parts.second);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -132,12 +132,14 @@ public class StatisticsRecordingFragment extends Fragment implements TrackDataLi
|
|||||||
private TextView speedMovingLabel;
|
private TextView speedMovingLabel;
|
||||||
private TextView speedMovingValue;
|
private TextView speedMovingValue;
|
||||||
private TextView speedMovingUnit;
|
private TextView speedMovingUnit;
|
||||||
private Group elevationGroup;
|
|
||||||
private TextView speedLabel;
|
private TextView speedLabel;
|
||||||
private TextView speedValue;
|
private TextView speedValue;
|
||||||
private TextView speedUnit;
|
private TextView speedUnit;
|
||||||
private TextView elevationValue;
|
private Group elevationGroup;
|
||||||
private TextView elevationUnit;
|
private TextView elevationCurrentValue;
|
||||||
|
private TextView elevationCurrentUnit;
|
||||||
|
private TextView elevationTotalGainValue;
|
||||||
|
private TextView elevationTotalGainUnit;
|
||||||
private Group coordinateGroup;
|
private Group coordinateGroup;
|
||||||
private TextView latitudeValue;
|
private TextView latitudeValue;
|
||||||
private TextView longitudeValue;
|
private TextView longitudeValue;
|
||||||
@@ -178,14 +180,16 @@ public class StatisticsRecordingFragment extends Fragment implements TrackDataLi
|
|||||||
speedMovingValue = view.findViewById(R.id.stats_moving_speed_value);
|
speedMovingValue = view.findViewById(R.id.stats_moving_speed_value);
|
||||||
speedMovingUnit = view.findViewById(R.id.stats_moving_speed_unit);
|
speedMovingUnit = view.findViewById(R.id.stats_moving_speed_unit);
|
||||||
|
|
||||||
elevationGroup = view.findViewById(R.id.stats_elevation_current_group);
|
|
||||||
|
|
||||||
speedLabel = view.findViewById(R.id.stats_speed_label);
|
speedLabel = view.findViewById(R.id.stats_speed_label);
|
||||||
speedValue = view.findViewById(R.id.stats_speed_value);
|
speedValue = view.findViewById(R.id.stats_speed_value);
|
||||||
speedUnit = view.findViewById(R.id.stats_speed_unit);
|
speedUnit = view.findViewById(R.id.stats_speed_unit);
|
||||||
|
|
||||||
elevationValue = view.findViewById(R.id.stats_elevation_current_value);
|
elevationGroup = view.findViewById(R.id.stats_elevation_group);
|
||||||
elevationUnit = view.findViewById(R.id.stats_elevation_current_unit);
|
elevationCurrentValue = view.findViewById(R.id.stats_elevation_current_value);
|
||||||
|
elevationCurrentUnit = view.findViewById(R.id.stats_elevation_current_unit);
|
||||||
|
|
||||||
|
elevationTotalGainValue = view.findViewById(R.id.stats_elevation_gain_value);
|
||||||
|
elevationTotalGainUnit = view.findViewById(R.id.stats_elevation_gain_unit);
|
||||||
|
|
||||||
coordinateGroup = view.findViewById(R.id.stats_coordinate_group);
|
coordinateGroup = view.findViewById(R.id.stats_coordinate_group);
|
||||||
|
|
||||||
@@ -276,14 +280,16 @@ public class StatisticsRecordingFragment extends Fragment implements TrackDataLi
|
|||||||
speedMovingValue = null;
|
speedMovingValue = null;
|
||||||
speedMovingUnit = null;
|
speedMovingUnit = null;
|
||||||
|
|
||||||
elevationGroup = null;
|
|
||||||
|
|
||||||
speedLabel = null;
|
speedLabel = null;
|
||||||
speedValue = null;
|
speedValue = null;
|
||||||
speedUnit = null;
|
speedUnit = null;
|
||||||
|
|
||||||
elevationValue = null;
|
elevationGroup = null;
|
||||||
elevationUnit = null;
|
elevationCurrentValue = null;
|
||||||
|
elevationCurrentUnit = null;
|
||||||
|
|
||||||
|
elevationTotalGainValue = null;
|
||||||
|
elevationTotalGainUnit = null;
|
||||||
|
|
||||||
coordinateGroup = null;
|
coordinateGroup = null;
|
||||||
|
|
||||||
@@ -406,20 +412,25 @@ public class StatisticsRecordingFragment extends Fragment implements TrackDataLi
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Tries to fetch most recent {@link SensorDataSet} from {@link de.dennisguse.opentracks.services.TrackRecordingService}.
|
* Tries to fetch most recent {@link SensorDataSet} from {@link de.dennisguse.opentracks.services.TrackRecordingService}.
|
||||||
|
* Also sets elevation gain.
|
||||||
*/
|
*/
|
||||||
private void updateSensorDataUI() {
|
private void updateSensorDataUI() {
|
||||||
TrackRecordingServiceInterface trackRecordingService = trackRecordingServiceConnection.getServiceIfBound();
|
TrackRecordingServiceInterface trackRecordingService = trackRecordingServiceConnection.getServiceIfBound();
|
||||||
|
|
||||||
SensorDataSet sensorDataSet = null;
|
SensorDataSet sensorDataSet = null;
|
||||||
|
Float elevationGain_m = null;
|
||||||
if (trackRecordingService == null) {
|
if (trackRecordingService == null) {
|
||||||
Log.d(TAG, "Cannot get the track recording service.");
|
Log.d(TAG, "Cannot get the track recording service.");
|
||||||
} else {
|
} else {
|
||||||
sensorDataSet = trackRecordingService.getSensorData();
|
sensorDataSet = trackRecordingService.getSensorData();
|
||||||
|
elevationGain_m = trackRecordingService.getElevationGain_m();
|
||||||
}
|
}
|
||||||
|
|
||||||
setHeartRateSensorData(sensorDataSet);
|
setHeartRateSensorData(sensorDataSet);
|
||||||
setCadenceSensorData(sensorDataSet);
|
setCadenceSensorData(sensorDataSet);
|
||||||
setSpeedSensorData(sensorDataSet, isSelectedTrackRecording());
|
setSpeedSensorData(sensorDataSet, isSelectedTrackRecording());
|
||||||
|
|
||||||
|
setTotalElevationGain(elevationGain_m);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setHeartRateSensorData(SensorDataSet sensorDataSet) {
|
private void setHeartRateSensorData(SensorDataSet sensorDataSet) {
|
||||||
@@ -468,6 +479,26 @@ public class StatisticsRecordingFragment extends Fragment implements TrackDataLi
|
|||||||
cadenceValueView.setText(sensorValue);
|
cadenceValueView.setText(sensorValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set elevation gain
|
||||||
|
private void setTotalElevationGain(Float elevationGain_m) {
|
||||||
|
//TODO Check if we can distribute the total elevation gain via trackStatistics instead of doing some computation in the UI layer.
|
||||||
|
boolean metricUnits = PreferencesUtils.isMetricUnits(getContext());
|
||||||
|
|
||||||
|
Float totalElevationGain = elevationGain_m;
|
||||||
|
|
||||||
|
if (lastTrackStatistics != null && lastTrackStatistics.hasTotalElevationGain()) {
|
||||||
|
if (elevationGain_m == null) {
|
||||||
|
totalElevationGain = lastTrackStatistics.getTotalElevationGain();
|
||||||
|
} else {
|
||||||
|
totalElevationGain += lastTrackStatistics.getTotalElevationGain();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Pair<String, String> parts = StringUtils.formatElevation(getContext(), totalElevationGain, metricUnits);
|
||||||
|
elevationTotalGainValue.setText(parts.first);
|
||||||
|
elevationTotalGainUnit.setText(parts.second);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If cadence and hear rate groups are invisible then sensor horizontal line hast to be invisible too.
|
* If cadence and hear rate groups are invisible then sensor horizontal line hast to be invisible too.
|
||||||
*/
|
*/
|
||||||
@@ -475,7 +506,6 @@ public class StatisticsRecordingFragment extends Fragment implements TrackDataLi
|
|||||||
if (cadenceGroup.getVisibility() != View.VISIBLE && heartRateGroup.getVisibility() != View.VISIBLE) {
|
if (cadenceGroup.getVisibility() != View.VISIBLE && heartRateGroup.getVisibility() != View.VISIBLE) {
|
||||||
sensorHorizontalLine.setVisibility(View.GONE);
|
sensorHorizontalLine.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setSpeedSensorData(SensorDataSet sensorDataSet, boolean isRecording) {
|
private void setSpeedSensorData(SensorDataSet sensorDataSet, boolean isRecording) {
|
||||||
@@ -555,8 +585,9 @@ public class StatisticsRecordingFragment extends Fragment implements TrackDataLi
|
|||||||
speedMovingUnit.setText(parts.second);
|
speedMovingUnit.setText(parts.second);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make elevation visible?
|
// Set elevation (gain)
|
||||||
{
|
{
|
||||||
|
// Make elevation visible?
|
||||||
boolean showElevation = PreferencesUtils.isShowStatsElevation(getContext());
|
boolean showElevation = PreferencesUtils.isShowStatsElevation(getContext());
|
||||||
elevationGroup.setVisibility(showElevation ? View.VISIBLE : View.GONE);
|
elevationGroup.setVisibility(showElevation ? View.VISIBLE : View.GONE);
|
||||||
}
|
}
|
||||||
@@ -597,11 +628,11 @@ public class StatisticsRecordingFragment extends Fragment implements TrackDataLi
|
|||||||
elevationGroup.setVisibility(showElevation ? View.VISIBLE : View.GONE);
|
elevationGroup.setVisibility(showElevation ? View.VISIBLE : View.GONE);
|
||||||
|
|
||||||
if (showElevation) {
|
if (showElevation) {
|
||||||
double altitude = lastTrackPoint != null && lastTrackPoint.hasAltitude() ? lastTrackPoint.getAltitude() : Double.NaN;
|
// Current elevation
|
||||||
|
Float altitude = lastTrackPoint != null && lastTrackPoint.hasAltitude() ? (float) lastTrackPoint.getAltitude() : null;
|
||||||
Pair<String, String> parts = StringUtils.formatElevation(getContext(), altitude, metricUnits);
|
Pair<String, String> parts = StringUtils.formatElevation(getContext(), altitude, metricUnits);
|
||||||
|
elevationCurrentValue.setText(parts.first);
|
||||||
elevationValue.setText(parts.first);
|
elevationCurrentUnit.setText(parts.second);
|
||||||
elevationUnit.setText(parts.second);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set coordinate
|
// Set coordinate
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ public class KmlTrackWriter implements TrackWriter {
|
|||||||
public static final String EXTENDED_DATA_TYPE_CADENCE = "cadence";
|
public static final String EXTENDED_DATA_TYPE_CADENCE = "cadence";
|
||||||
public static final String EXTENDED_DATA_TYPE_HEART_RATE = "heart_rate";
|
public static final String EXTENDED_DATA_TYPE_HEART_RATE = "heart_rate";
|
||||||
public static final String EXTENDED_DATA_TYPE_POWER = "power";
|
public static final String EXTENDED_DATA_TYPE_POWER = "power";
|
||||||
|
public static final String EXTENDED_DATA_TYPE_ELEVATION_GAIN = "elevation_gain";
|
||||||
|
|
||||||
private static final String WAYPOINT_ICON = "http://maps.google.com/mapfiles/kml/pushpin/blue-pushpin.png";
|
private static final String WAYPOINT_ICON = "http://maps.google.com/mapfiles/kml/pushpin/blue-pushpin.png";
|
||||||
private static final String START_ICON = "http://maps.google.com/mapfiles/kml/paddle/grn-circle.png";
|
private static final String START_ICON = "http://maps.google.com/mapfiles/kml/paddle/grn-circle.png";
|
||||||
@@ -68,6 +69,7 @@ public class KmlTrackWriter implements TrackWriter {
|
|||||||
private final List<Float> powerList = new ArrayList<>();
|
private final List<Float> powerList = new ArrayList<>();
|
||||||
private final List<Float> cadenceList = new ArrayList<>();
|
private final List<Float> cadenceList = new ArrayList<>();
|
||||||
private final List<Float> heartRateList = new ArrayList<>();
|
private final List<Float> heartRateList = new ArrayList<>();
|
||||||
|
private final List<Float> elevationGainList = new ArrayList<>();
|
||||||
|
|
||||||
private TrackPoint startTrackPoint;
|
private TrackPoint startTrackPoint;
|
||||||
|
|
||||||
@@ -257,6 +259,9 @@ public class KmlTrackWriter implements TrackWriter {
|
|||||||
if (heartRateList.size() > 0) {
|
if (heartRateList.size() > 0) {
|
||||||
writeSimpleArrayData(heartRateList, EXTENDED_DATA_TYPE_HEART_RATE);
|
writeSimpleArrayData(heartRateList, EXTENDED_DATA_TYPE_HEART_RATE);
|
||||||
}
|
}
|
||||||
|
if (elevationGainList.size() > 0) {
|
||||||
|
writeSimpleArrayData(elevationGainList, EXTENDED_DATA_TYPE_ELEVATION_GAIN);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
printWriter.println("</SchemaData>");
|
printWriter.println("</SchemaData>");
|
||||||
printWriter.println("</ExtendedData>");
|
printWriter.println("</ExtendedData>");
|
||||||
@@ -287,6 +292,9 @@ public class KmlTrackWriter implements TrackWriter {
|
|||||||
if (trackPoint.hasPower()) {
|
if (trackPoint.hasPower()) {
|
||||||
powerList.add(trackPoint.getPower());
|
powerList.add(trackPoint.getPower());
|
||||||
}
|
}
|
||||||
|
if (trackPoint.hasElevationGain()) {
|
||||||
|
elevationGainList.add(trackPoint.getElevationGain());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,11 +63,12 @@ public class KmlFileTrackImporter extends AbstractFileTrackImporter {
|
|||||||
|
|
||||||
private boolean trackStarted = false;
|
private boolean trackStarted = false;
|
||||||
private String extendedDataType;
|
private String extendedDataType;
|
||||||
private ArrayList<TrackPoint> trackPoints;
|
private ArrayList<TrackPoint> trackPoints = new ArrayList<>();
|
||||||
private ArrayList<Float> speedList;
|
private ArrayList<Float> speedList = new ArrayList<>();
|
||||||
private ArrayList<Float> cadenceList;
|
private ArrayList<Float> cadenceList = new ArrayList<>();
|
||||||
private ArrayList<Float> heartRateList;
|
private ArrayList<Float> heartRateList = new ArrayList<>();
|
||||||
private ArrayList<Float> powerList;
|
private ArrayList<Float> powerList = new ArrayList<>();
|
||||||
|
private ArrayList<Float> elevationGainList = new ArrayList<>();
|
||||||
|
|
||||||
public KmlFileTrackImporter(Context context) {
|
public KmlFileTrackImporter(Context context) {
|
||||||
this(context, new ContentProviderUtils(context));
|
this(context, new ContentProviderUtils(context));
|
||||||
@@ -223,11 +224,12 @@ public class KmlFileTrackImporter extends AbstractFileTrackImporter {
|
|||||||
@Override
|
@Override
|
||||||
protected void onTrackSegmentStart() {
|
protected void onTrackSegmentStart() {
|
||||||
super.onTrackSegmentStart();
|
super.onTrackSegmentStart();
|
||||||
trackPoints = new ArrayList<>();
|
trackPoints.clear();
|
||||||
speedList = new ArrayList<>();
|
speedList.clear();
|
||||||
heartRateList = new ArrayList<>();
|
heartRateList.clear();
|
||||||
cadenceList = new ArrayList<>();
|
cadenceList.clear();
|
||||||
powerList = new ArrayList<>();
|
powerList.clear();
|
||||||
|
elevationGainList.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -250,6 +252,9 @@ public class KmlFileTrackImporter extends AbstractFileTrackImporter {
|
|||||||
if (i < powerList.size()) {
|
if (i < powerList.size()) {
|
||||||
trackPoint.setPower(powerList.get(i));
|
trackPoint.setPower(powerList.get(i));
|
||||||
}
|
}
|
||||||
|
if (i < elevationGainList.size()) {
|
||||||
|
trackPoint.setElevationGain(elevationGainList.get(i));
|
||||||
|
}
|
||||||
|
|
||||||
insertTrackPoint(trackPoint);
|
insertTrackPoint(trackPoint);
|
||||||
}
|
}
|
||||||
@@ -316,6 +321,9 @@ public class KmlFileTrackImporter extends AbstractFileTrackImporter {
|
|||||||
case KmlTrackWriter.EXTENDED_DATA_TYPE_CADENCE:
|
case KmlTrackWriter.EXTENDED_DATA_TYPE_CADENCE:
|
||||||
cadenceList.add(value);
|
cadenceList.add(value);
|
||||||
break;
|
break;
|
||||||
|
case KmlTrackWriter.EXTENDED_DATA_TYPE_ELEVATION_GAIN:
|
||||||
|
elevationGainList.add(value);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
Log.w(TAG, "Data from extended data " + extendedDataType + " is not (yet) supported.");
|
Log.w(TAG, "Data from extended data " + extendedDataType + " is not (yet) supported.");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ import de.dennisguse.opentracks.content.sensor.SensorDataSet;
|
|||||||
import de.dennisguse.opentracks.services.handlers.GpsStatusValue;
|
import de.dennisguse.opentracks.services.handlers.GpsStatusValue;
|
||||||
import de.dennisguse.opentracks.services.handlers.HandlerServer;
|
import de.dennisguse.opentracks.services.handlers.HandlerServer;
|
||||||
import de.dennisguse.opentracks.services.sensors.BluetoothRemoteSensorManager;
|
import de.dennisguse.opentracks.services.sensors.BluetoothRemoteSensorManager;
|
||||||
|
import de.dennisguse.opentracks.services.sensors.ElevationSumManager;
|
||||||
import de.dennisguse.opentracks.services.tasks.AnnouncementPeriodicTaskFactory;
|
import de.dennisguse.opentracks.services.tasks.AnnouncementPeriodicTaskFactory;
|
||||||
import de.dennisguse.opentracks.services.tasks.PeriodicTaskExecutor;
|
import de.dennisguse.opentracks.services.tasks.PeriodicTaskExecutor;
|
||||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||||
@@ -115,6 +116,7 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
|
|||||||
// The following variables are set when recording:
|
// The following variables are set when recording:
|
||||||
private WakeLock wakeLock;
|
private WakeLock wakeLock;
|
||||||
private BluetoothRemoteSensorManager remoteSensorManager;
|
private BluetoothRemoteSensorManager remoteSensorManager;
|
||||||
|
private ElevationSumManager elevationSumManager;
|
||||||
|
|
||||||
private TrackStatisticsUpdater trackStatisticsUpdater;
|
private TrackStatisticsUpdater trackStatisticsUpdater;
|
||||||
private TrackPoint lastTrackPoint;
|
private TrackPoint lastTrackPoint;
|
||||||
@@ -174,6 +176,11 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
|
|||||||
remoteSensorManager = null;
|
remoteSensorManager = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (elevationSumManager != null) {
|
||||||
|
elevationSumManager.stop(this);
|
||||||
|
elevationSumManager = null;
|
||||||
|
}
|
||||||
|
|
||||||
// Reverse order from onCreate
|
// Reverse order from onCreate
|
||||||
showNotification(false); //TODO Why?
|
showNotification(false); //TODO Why?
|
||||||
|
|
||||||
@@ -371,6 +378,10 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
|
|||||||
// Update instance variables
|
// Update instance variables
|
||||||
remoteSensorManager = new BluetoothRemoteSensorManager(this);
|
remoteSensorManager = new BluetoothRemoteSensorManager(this);
|
||||||
remoteSensorManager.start();
|
remoteSensorManager.start();
|
||||||
|
|
||||||
|
elevationSumManager = new ElevationSumManager();
|
||||||
|
elevationSumManager.start(this);
|
||||||
|
|
||||||
lastTrackPoint = null;
|
lastTrackPoint = null;
|
||||||
isIdle = false;
|
isIdle = false;
|
||||||
|
|
||||||
@@ -456,6 +467,11 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
|
|||||||
remoteSensorManager.stop();
|
remoteSensorManager.stop();
|
||||||
remoteSensorManager = null;
|
remoteSensorManager = null;
|
||||||
}
|
}
|
||||||
|
if (elevationSumManager != null) {
|
||||||
|
elevationSumManager.stop(this);
|
||||||
|
elevationSumManager = null;
|
||||||
|
}
|
||||||
|
|
||||||
lastTrackPoint = null;
|
lastTrackPoint = null;
|
||||||
|
|
||||||
handlerServer.stop(this);
|
handlerServer.stop(this);
|
||||||
@@ -556,15 +572,17 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
|
|||||||
insertTrackPoint(track, TrackPoint.createPause());
|
insertTrackPoint(track, TrackPoint.createPause());
|
||||||
|
|
||||||
insertTrackPoint(track, trackPoint);
|
insertTrackPoint(track, trackPoint);
|
||||||
isIdle = false;
|
|
||||||
|
|
||||||
|
isIdle = false;
|
||||||
lastTrackPoint = trackPoint;
|
lastTrackPoint = trackPoint;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (trackPoint.hasSensorData() || distanceToLastTrackLocation >= recordingDistanceInterval) {
|
if (trackPoint.hasSensorData() || distanceToLastTrackLocation >= recordingDistanceInterval) {
|
||||||
insertTrackPointIfNewer(track, lastTrackPoint);
|
insertTrackPointIfNewer(track, lastTrackPoint);
|
||||||
|
|
||||||
insertTrackPoint(track, trackPoint);
|
insertTrackPoint(track, trackPoint);
|
||||||
|
|
||||||
isIdle = false;
|
isIdle = false;
|
||||||
|
|
||||||
lastTrackPoint = trackPoint;
|
lastTrackPoint = trackPoint;
|
||||||
@@ -573,7 +591,9 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
|
|||||||
|
|
||||||
if (!isIdle && !TrackPointUtils.isMoving(trackPoint)) {
|
if (!isIdle && !TrackPointUtils.isMoving(trackPoint)) {
|
||||||
insertTrackPointIfNewer(track, lastTrackPoint);
|
insertTrackPointIfNewer(track, lastTrackPoint);
|
||||||
|
|
||||||
insertTrackPoint(track, trackPoint);
|
insertTrackPoint(track, trackPoint);
|
||||||
|
|
||||||
isIdle = true;
|
isIdle = true;
|
||||||
|
|
||||||
lastTrackPoint = trackPoint;
|
lastTrackPoint = trackPoint;
|
||||||
@@ -582,7 +602,9 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
|
|||||||
|
|
||||||
if (isIdle && TrackPointUtils.isMoving(trackPoint)) {
|
if (isIdle && TrackPointUtils.isMoving(trackPoint)) {
|
||||||
insertTrackPointIfNewer(track, lastTrackPoint);
|
insertTrackPointIfNewer(track, lastTrackPoint);
|
||||||
|
|
||||||
insertTrackPoint(track, trackPoint);
|
insertTrackPoint(track, trackPoint);
|
||||||
|
|
||||||
isIdle = false;
|
isIdle = false;
|
||||||
|
|
||||||
lastTrackPoint = trackPoint;
|
lastTrackPoint = trackPoint;
|
||||||
@@ -632,6 +654,10 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
|
|||||||
*/
|
*/
|
||||||
private void insertTrackPoint(@NonNull Track track, @NonNull TrackPoint trackPoint) {
|
private void insertTrackPoint(@NonNull Track track, @NonNull TrackPoint trackPoint) {
|
||||||
try {
|
try {
|
||||||
|
if (elevationSumManager != null) {
|
||||||
|
trackPoint.setElevationGain(elevationSumManager.getElevationGain_m());
|
||||||
|
elevationSumManager.reset();
|
||||||
|
}
|
||||||
contentProviderUtils.insertTrackPoint(trackPoint, track.getId());
|
contentProviderUtils.insertTrackPoint(trackPoint, track.getId());
|
||||||
trackStatisticsUpdater.addTrackPoint(trackPoint, recordingDistanceInterval);
|
trackStatisticsUpdater.addTrackPoint(trackPoint, recordingDistanceInterval);
|
||||||
updateTrackTotalTime(track);
|
updateTrackTotalTime(track);
|
||||||
@@ -672,6 +698,17 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the relative elevation gain (since last trackpoint).
|
||||||
|
*/
|
||||||
|
Float getElevationGain_m() {
|
||||||
|
if (elevationSumManager == null || !elevationSumManager.isConnected()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return elevationSumManager.getElevationGain_m();
|
||||||
|
}
|
||||||
|
|
||||||
private void showNotification(boolean isGpsStarted) {
|
private void showNotification(boolean isGpsStarted) {
|
||||||
if (isRecording()) {
|
if (isRecording()) {
|
||||||
Intent intent = IntentUtils.newIntent(this, TrackRecordingActivity.class)
|
Intent intent = IntentUtils.newIntent(this, TrackRecordingActivity.class)
|
||||||
|
|||||||
@@ -99,6 +99,11 @@ class TrackRecordingServiceBinder extends android.os.Binder implements TrackReco
|
|||||||
return trackRecordingService.getSensorDataSet();
|
return trackRecordingService.getSensorDataSet();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Float getElevationGain_m() {
|
||||||
|
return trackRecordingService.getElevationGain_m();
|
||||||
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
@Override
|
@Override
|
||||||
public void setRemoteSensorManager(BluetoothRemoteSensorManager remoteSensorManager) {
|
public void setRemoteSensorManager(BluetoothRemoteSensorManager remoteSensorManager) {
|
||||||
|
|||||||
@@ -65,12 +65,19 @@ public interface TrackRecordingServiceInterface {
|
|||||||
Marker.Id insertMarker(String name, String category, String description, String photoUrl);
|
Marker.Id insertMarker(String name, String category, String description, String photoUrl);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the current sensor data. Returns null if there is no data.
|
* Gets the current sensor data.
|
||||||
*
|
*
|
||||||
* @return SensorDataSet object.
|
* @return SensorDataSet object or null.
|
||||||
*/
|
*/
|
||||||
SensorDataSet getSensorData();
|
SensorDataSet getSensorData();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the current elevation gain.
|
||||||
|
*
|
||||||
|
* @return elevation gain in m or null.
|
||||||
|
*/
|
||||||
|
Float getElevationGain_m();
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
void setRemoteSensorManager(BluetoothRemoteSensorManager remoteSensorManager);
|
void setRemoteSensorManager(BluetoothRemoteSensorManager remoteSensorManager);
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,117 @@
|
|||||||
|
package de.dennisguse.opentracks.services.sensors;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.hardware.Sensor;
|
||||||
|
import android.hardware.SensorEvent;
|
||||||
|
import android.hardware.SensorEventListener;
|
||||||
|
import android.hardware.SensorManager;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.annotation.VisibleForTesting;
|
||||||
|
|
||||||
|
import de.dennisguse.opentracks.util.PressureSensorUtils;
|
||||||
|
import de.dennisguse.opentracks.util.UnitConversions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Estimates the elevation gain and elevation loss using the device's pressure sensor (i.e., barometer).
|
||||||
|
*/
|
||||||
|
public class ElevationSumManager implements SensorEventListener {
|
||||||
|
|
||||||
|
private static final String TAG = ElevationSumManager.class.getSimpleName();
|
||||||
|
|
||||||
|
private static final int SAMPLING_RATE = 3 * (int) UnitConversions.ONE_SECOND_US;
|
||||||
|
|
||||||
|
private boolean isConnected = false;
|
||||||
|
|
||||||
|
private float lastAcceptedPressureValue_hPa;
|
||||||
|
|
||||||
|
private float lastSeenSensorValue_hPa;
|
||||||
|
|
||||||
|
private float elevationGain_m;
|
||||||
|
private float elevationLoss_m;
|
||||||
|
|
||||||
|
public void start(Context context) {
|
||||||
|
SensorManager sensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
|
||||||
|
|
||||||
|
Sensor pressureSensor = sensorManager.getDefaultSensor(Sensor.TYPE_PRESSURE);
|
||||||
|
if (pressureSensor == null) {
|
||||||
|
Log.w(TAG, "No pressure sensor available.");
|
||||||
|
isConnected = false;
|
||||||
|
} else {
|
||||||
|
isConnected = sensorManager.registerListener(this, pressureSensor, SAMPLING_RATE);
|
||||||
|
}
|
||||||
|
|
||||||
|
lastAcceptedPressureValue_hPa = Float.NaN;
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void stop(Context context) {
|
||||||
|
Log.d(TAG, "Stop");
|
||||||
|
|
||||||
|
SensorManager sensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
|
||||||
|
sensorManager.unregisterListener(this);
|
||||||
|
|
||||||
|
isConnected = false;
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isConnected() {
|
||||||
|
return isConnected;
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
public void setConnected(boolean isConnected) {
|
||||||
|
this.isConnected = isConnected;
|
||||||
|
}
|
||||||
|
|
||||||
|
public @Nullable
|
||||||
|
Float getElevationGain_m() {
|
||||||
|
return isConnected ? elevationGain_m : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public @Nullable
|
||||||
|
Float getElevationLoss_m() {
|
||||||
|
return isConnected ? elevationLoss_m : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reset() {
|
||||||
|
Log.d(TAG, "Reset");
|
||||||
|
elevationGain_m = 0;
|
||||||
|
elevationLoss_m = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAccuracyChanged(Sensor sensor, int accuracy) {
|
||||||
|
Log.w(TAG, "Sensor accuracy changes are (currently) ignored.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSensorChanged(SensorEvent event) {
|
||||||
|
if (!isConnected) {
|
||||||
|
Log.w(TAG, "Not connected to sensor, cannot process data.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
onSensorValueChanged(event.values[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
void onSensorValueChanged(float value_hPa) {
|
||||||
|
if (Float.isNaN(lastAcceptedPressureValue_hPa)) {
|
||||||
|
lastAcceptedPressureValue_hPa = value_hPa;
|
||||||
|
lastSeenSensorValue_hPa = value_hPa;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
PressureSensorUtils.ElevationChange elevationChange = PressureSensorUtils.computeChangesWithSmoothing_m(lastAcceptedPressureValue_hPa, lastSeenSensorValue_hPa, value_hPa);
|
||||||
|
if (elevationChange != null) {
|
||||||
|
elevationGain_m += elevationChange.getElevationGain_m();
|
||||||
|
elevationLoss_m += elevationChange.getElevationLoss_m();
|
||||||
|
lastAcceptedPressureValue_hPa = elevationChange.getCurrentSensorValue_hPa();
|
||||||
|
}
|
||||||
|
|
||||||
|
lastSeenSensorValue_hPa = value_hPa;
|
||||||
|
|
||||||
|
Log.v(TAG, "elevation gain: " + elevationGain_m);
|
||||||
|
}
|
||||||
|
}
|
||||||
+3
-3
@@ -19,13 +19,13 @@ package de.dennisguse.opentracks.stats;
|
|||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class maintains a buffer of doubles.
|
* This class maintains a ring buffer of doubles.
|
||||||
* This buffer is a convenient class for storing a series of doubles and calculating information about them.
|
* This buffer is a convenient class for storing a series of doubles and calculating information about them.
|
||||||
* This is a FIFO buffer.
|
* This is a FIFO buffer.
|
||||||
*
|
*
|
||||||
* @author Sandor Dornbush
|
* @author Sandor Dornbush
|
||||||
*/
|
*/
|
||||||
class DoubleBuffer {
|
class DoubleRingBuffer {
|
||||||
|
|
||||||
// The sliding buffer of doubles.
|
// The sliding buffer of doubles.
|
||||||
private final double[] buffer;
|
private final double[] buffer;
|
||||||
@@ -41,7 +41,7 @@ class DoubleBuffer {
|
|||||||
*
|
*
|
||||||
* @param size the size
|
* @param size the size
|
||||||
*/
|
*/
|
||||||
DoubleBuffer(int size) {
|
DoubleRingBuffer(int size) {
|
||||||
if (size < 1) {
|
if (size < 1) {
|
||||||
throw new IllegalArgumentException("The buffer size must be greater than 1.");
|
throw new IllegalArgumentException("The buffer size must be greater than 1.");
|
||||||
}
|
}
|
||||||
@@ -17,6 +17,7 @@
|
|||||||
package de.dennisguse.opentracks.stats;
|
package de.dennisguse.opentracks.stats;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Statistical data about a {@link de.dennisguse.opentracks.content.data.Track}.
|
* Statistical data about a {@link de.dennisguse.opentracks.content.data.Track}.
|
||||||
@@ -24,6 +25,7 @@ import androidx.annotation.NonNull;
|
|||||||
*
|
*
|
||||||
* @author Rodrigo Damazio
|
* @author Rodrigo Damazio
|
||||||
*/
|
*/
|
||||||
|
//TODO Use null instead of Double.isInfinite
|
||||||
public class TrackStatistics {
|
public class TrackStatistics {
|
||||||
|
|
||||||
// The min and max elevation (meters) seen on this track.
|
// The min and max elevation (meters) seen on this track.
|
||||||
@@ -42,7 +44,7 @@ public class TrackStatistics {
|
|||||||
// The maximum speed (meters/second) that we believe is valid.
|
// The maximum speed (meters/second) that we believe is valid.
|
||||||
private double maxSpeed_mps;
|
private double maxSpeed_mps;
|
||||||
// The total elevation gained (meters).
|
// The total elevation gained (meters).
|
||||||
private double totalElevationGain_m;
|
private Float totalElevationGain_m = null;
|
||||||
|
|
||||||
public TrackStatistics() {
|
public TrackStatistics() {
|
||||||
}
|
}
|
||||||
@@ -80,7 +82,15 @@ public class TrackStatistics {
|
|||||||
elevationExtremities.update(other.elevationExtremities.getMin());
|
elevationExtremities.update(other.elevationExtremities.getMin());
|
||||||
elevationExtremities.update(other.elevationExtremities.getMax());
|
elevationExtremities.update(other.elevationExtremities.getMax());
|
||||||
}
|
}
|
||||||
totalElevationGain_m += other.totalElevationGain_m;
|
if (totalElevationGain_m == null) {
|
||||||
|
if (other.totalElevationGain_m != null) {
|
||||||
|
totalElevationGain_m = other.totalElevationGain_m;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (other.totalElevationGain_m != null) {
|
||||||
|
totalElevationGain_m += other.totalElevationGain_m;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -227,18 +237,23 @@ public class TrackStatistics {
|
|||||||
elevationExtremities.update(elevation_m);
|
elevationExtremities.update(elevation_m);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public boolean hasTotalElevationGain() {
|
||||||
* Gets the total elevation gain in meters. This is calculated as the sum of all positive differences in the smoothed elevation.
|
return totalElevationGain_m != null;
|
||||||
*/
|
}
|
||||||
public double getTotalElevationGain() {
|
|
||||||
|
public @Nullable
|
||||||
|
Float getTotalElevationGain() {
|
||||||
return totalElevationGain_m;
|
return totalElevationGain_m;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setTotalElevationGain(double totalElevationGain_m) {
|
public void setTotalElevationGain(float totalElevationGain_m) {
|
||||||
this.totalElevationGain_m = totalElevationGain_m;
|
this.totalElevationGain_m = totalElevationGain_m;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addTotalElevationGain(double gain_m) {
|
public void addTotalElevationGain(float gain_m) {
|
||||||
|
if (totalElevationGain_m == null) {
|
||||||
|
totalElevationGain_m = 0f;
|
||||||
|
}
|
||||||
totalElevationGain_m += gain_m;
|
totalElevationGain_m += gain_m;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -62,9 +62,9 @@ public class TrackStatisticsUpdater {
|
|||||||
private final TrackStatistics trackStatistics;
|
private final TrackStatistics trackStatistics;
|
||||||
|
|
||||||
// A buffer of the recent elevation readings (m)
|
// A buffer of the recent elevation readings (m)
|
||||||
private final DoubleBuffer elevationBuffer_m = new DoubleBuffer(ELEVATION_SMOOTHING_FACTOR);
|
private final DoubleRingBuffer elevationBuffer_m = new DoubleRingBuffer(ELEVATION_SMOOTHING_FACTOR);
|
||||||
// A buffer of the recent speed readings (m/s) for calculating max speed
|
// A buffer of the recent speed readings (m/s) for calculating max speed
|
||||||
private final DoubleBuffer speedBuffer_ms = new DoubleBuffer(SPEED_SMOOTHING_FACTOR);
|
private final DoubleRingBuffer speedBuffer_ms = new DoubleRingBuffer(SPEED_SMOOTHING_FACTOR);
|
||||||
|
|
||||||
// The current segment's statistics
|
// The current segment's statistics
|
||||||
private TrackStatistics currentSegment;
|
private TrackStatistics currentSegment;
|
||||||
@@ -112,7 +112,7 @@ public class TrackStatisticsUpdater {
|
|||||||
* Adds a trackPoint.
|
* Adds a trackPoint.
|
||||||
* TODO: This assume trackPoint has a valid time.
|
* TODO: This assume trackPoint has a valid time.
|
||||||
*
|
*
|
||||||
* @param trackPoint the trackPoint
|
* @param trackPoint the trackPoint
|
||||||
* @param minRecordingDistance the min recording distance
|
* @param minRecordingDistance the min recording distance
|
||||||
*/
|
*/
|
||||||
public void addTrackPoint(TrackPoint trackPoint, int minRecordingDistance) {
|
public void addTrackPoint(TrackPoint trackPoint, int minRecordingDistance) {
|
||||||
@@ -134,8 +134,16 @@ public class TrackStatisticsUpdater {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO Use Barometer to compute elevation gain.
|
//Update absolute (GPS-based) elevation
|
||||||
double elevationDifference = trackPoint.hasAltitude() ? updateElevation(trackPoint.getAltitude()) : 0.0;
|
if (trackPoint.hasAltitude()) {
|
||||||
|
updateAbsoluteElevation(trackPoint.getAltitude());
|
||||||
|
}
|
||||||
|
|
||||||
|
//Get elevation gain
|
||||||
|
if (trackPoint.hasElevationGain()) {
|
||||||
|
currentSegment.addTotalElevationGain(trackPoint.getElevationGain());
|
||||||
|
Log.d(TAG, "elevation gain: " + trackPoint.getElevationGain());
|
||||||
|
}
|
||||||
|
|
||||||
if (lastTrackPoint == null || lastMovingTrackPoint == null) {
|
if (lastTrackPoint == null || lastMovingTrackPoint == null) {
|
||||||
lastTrackPoint = trackPoint;
|
lastTrackPoint = trackPoint;
|
||||||
@@ -207,23 +215,21 @@ public class TrackStatisticsUpdater {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates an elevation reading. Returns the difference.
|
* Updates an elevation reading.
|
||||||
*
|
*
|
||||||
* @param elevation the elevation
|
* @param elevation the elevation
|
||||||
|
* @return the difference
|
||||||
*/
|
*/
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
private double updateElevation(double elevation) {
|
private double updateAbsoluteElevation(double elevation) {
|
||||||
// Update elevation using the smoothed average
|
// Update elevation using the smoothed average
|
||||||
double oldAverage = elevationBuffer_m.getAverage();
|
double oldAverage = elevationBuffer_m.getAverage();
|
||||||
elevationBuffer_m.setNext(elevation);
|
elevationBuffer_m.setNext(elevation);
|
||||||
double newAverage = elevationBuffer_m.getAverage();
|
double newAverage = elevationBuffer_m.getAverage();
|
||||||
|
|
||||||
currentSegment.updateElevationExtremities(newAverage);
|
currentSegment.updateElevationExtremities(newAverage);
|
||||||
double difference = newAverage - oldAverage;
|
|
||||||
if (difference > 0) {
|
return newAverage - oldAverage;
|
||||||
currentSegment.addTotalElevationGain(difference);
|
|
||||||
}
|
|
||||||
return difference;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private TrackStatistics init(long time) {
|
private TrackStatistics init(long time) {
|
||||||
|
|||||||
@@ -0,0 +1,83 @@
|
|||||||
|
package de.dennisguse.opentracks.util;
|
||||||
|
|
||||||
|
import android.hardware.SensorManager;
|
||||||
|
|
||||||
|
public class PressureSensorUtils {
|
||||||
|
|
||||||
|
//Everything above is considered a meaningful change in elevation.
|
||||||
|
private static float ELEVATION_CHANGE_DIFF_M = 3.0f;
|
||||||
|
|
||||||
|
private static float EXPONENTIAL_SMOOTHING = 0.3f;
|
||||||
|
|
||||||
|
private PressureSensorUtils() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ElevationChange {
|
||||||
|
|
||||||
|
private float currentSensorValue_hPa;
|
||||||
|
|
||||||
|
private float elevationChange_m;
|
||||||
|
|
||||||
|
public ElevationChange(float currentSensorValue_hPa, float elevationChange_m) {
|
||||||
|
this.currentSensorValue_hPa = currentSensorValue_hPa;
|
||||||
|
this.elevationChange_m = elevationChange_m;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getCurrentSensorValue_hPa() {
|
||||||
|
return currentSensorValue_hPa;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getElevationChange_m() {
|
||||||
|
return elevationChange_m;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getElevationGain_m() {
|
||||||
|
return elevationChange_m > 0 ? elevationChange_m : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getElevationLoss_m() {
|
||||||
|
return elevationChange_m < 0 ? elevationChange_m : 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies exponential smoothing to sensor value before computation.
|
||||||
|
*/
|
||||||
|
public static ElevationChange computeChangesWithSmoothing_m(float lastAcceptedSensorValue_hPa, float lastSeenSensorValue_hPa, float currentSensorValue_hPa) {
|
||||||
|
float nextSensorValue_hPa = EXPONENTIAL_SMOOTHING * currentSensorValue_hPa + (1 - EXPONENTIAL_SMOOTHING) * lastSeenSensorValue_hPa;
|
||||||
|
|
||||||
|
return computeChanges_m(lastAcceptedSensorValue_hPa, nextSensorValue_hPa);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Computes the elevation gain and elevation loss.
|
||||||
|
*
|
||||||
|
* @return null if no meaningful elevation change occurred.
|
||||||
|
*/
|
||||||
|
public static ElevationChange computeChanges_m(float lastAcceptedSensorValue_hPa, float currentSensorValue_hPa) {
|
||||||
|
float lastSensorValue_m = SensorManager.getAltitude(SensorManager.PRESSURE_STANDARD_ATMOSPHERE, lastAcceptedSensorValue_hPa);
|
||||||
|
float currentSensorValue_m = SensorManager.getAltitude(SensorManager.PRESSURE_STANDARD_ATMOSPHERE, currentSensorValue_hPa);
|
||||||
|
|
||||||
|
float elevationChange_m = currentSensorValue_m - lastSensorValue_m;
|
||||||
|
if (Math.abs(elevationChange_m) < ELEVATION_CHANGE_DIFF_M) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Limit elevation change by ELEVATION_CHANGE_DIFF and computes pressure value accordingly.
|
||||||
|
ElevationChange elevationChange = new ElevationChange(currentSensorValue_hPa, elevationChange_m);
|
||||||
|
if (elevationChange.getElevationGain_m() > 0) {
|
||||||
|
return new ElevationChange(getBarometricPressure(SensorManager.PRESSURE_STANDARD_ATMOSPHERE, lastSensorValue_m + ELEVATION_CHANGE_DIFF_M), ELEVATION_CHANGE_DIFF_M);
|
||||||
|
} else {
|
||||||
|
return new ElevationChange(getBarometricPressure(SensorManager.PRESSURE_STANDARD_ATMOSPHERE, lastSensorValue_m - ELEVATION_CHANGE_DIFF_M), -1 * ELEVATION_CHANGE_DIFF_M);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Barometeric pressure to elevation estimation; inverts of SensorManager.getAltitude(float, float)
|
||||||
|
* https://de.wikipedia.org/wiki/Barometrische_H%C3%B6henformel#Internationale_H%C3%B6henformel
|
||||||
|
* {\color{White} p(h)} = p_0 \cdot \left( 1 - \frac{0{,}0065 \frac{\mathrm K}{\mathrm m} \cdot h}{T_0\ } \right)^{5{,}255}
|
||||||
|
*/
|
||||||
|
private static float getBarometricPressure(float p0, float altitude_m) {
|
||||||
|
return (float) (p0 * Math.pow(1.0 - 0.0065 * altitude_m / 288.15, 5.255f));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -323,21 +323,21 @@ public class StringUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets an elevation value.
|
* Sets an elevation_m value.
|
||||||
*
|
*
|
||||||
* @param context the context
|
* @param context the context
|
||||||
* @param elevation the elevation in meters
|
* @param elevation_m the elevation_m
|
||||||
* @param metricUnits true if metric units
|
* @param metricUnits true if metric units
|
||||||
* @return the formatted elevation (or null) and it's unit as {@link Pair}
|
* @return the formatted elevation_m (or null) and it's unit as {@link Pair}
|
||||||
*/
|
*/
|
||||||
public static Pair<String, String> formatElevation(Context context, double elevation, boolean metricUnits) {
|
public static Pair<String, String> formatElevation(Context context, Float elevation_m, boolean metricUnits) {
|
||||||
String value = context.getString(R.string.value_unknown);
|
String value = context.getString(R.string.value_unknown);
|
||||||
String unit = context.getString(metricUnits ? R.string.unit_meter : R.string.unit_feet);
|
String unit = context.getString(metricUnits ? R.string.unit_meter : R.string.unit_feet);
|
||||||
if (!Double.isNaN(elevation) && !Double.isInfinite(elevation)) {
|
if (elevation_m != null) {
|
||||||
if (!metricUnits) {
|
if (!metricUnits) {
|
||||||
elevation *= UnitConversions.M_TO_FT;
|
elevation_m *= (float) UnitConversions.M_TO_FT;
|
||||||
}
|
}
|
||||||
value = StringUtils.formatDecimal(elevation, 0);
|
value = StringUtils.formatDecimal(elevation_m, 0);
|
||||||
}
|
}
|
||||||
return new Pair<>(value, unit);
|
return new Pair<>(value, unit);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,8 @@ public class UnitConversions {
|
|||||||
public static final long S_TO_MS = 1000;
|
public static final long S_TO_MS = 1000;
|
||||||
|
|
||||||
// Time
|
// Time
|
||||||
// 1 second in milliseconds
|
// TODO Use int instead of long.
|
||||||
|
public static final long ONE_SECOND_US = UnitConversions.S_TO_MS * 1000;
|
||||||
public static final long ONE_SECOND_MS = UnitConversions.S_TO_MS;
|
public static final long ONE_SECOND_MS = UnitConversions.S_TO_MS;
|
||||||
public static final long ONE_MINUTE_MS = (long) (UnitConversions.MIN_TO_S * UnitConversions.S_TO_MS);
|
public static final long ONE_MINUTE_MS = (long) (UnitConversions.MIN_TO_S * UnitConversions.S_TO_MS);
|
||||||
public static final long ONE_HOUR_MS = 60 * ONE_MINUTE_MS;
|
public static final long ONE_HOUR_MS = 60 * ONE_MINUTE_MS;
|
||||||
|
|||||||
@@ -116,6 +116,14 @@
|
|||||||
app:layout_constraintTop_toBottomOf="@+id/stats_moving_time_label"
|
app:layout_constraintTop_toBottomOf="@+id/stats_moving_time_label"
|
||||||
tools:text="00:00:00" />
|
tools:text="00:00:00" />
|
||||||
|
|
||||||
|
<!-- Barrier -->
|
||||||
|
<androidx.constraintlayout.widget.Barrier
|
||||||
|
android:id="@+id/stats_time_barrier"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:barrierDirection="bottom"
|
||||||
|
app:constraint_referenced_ids="stats_total_time_value,stats_moving_time_value" />
|
||||||
|
|
||||||
<!-- Horizontal line -->
|
<!-- Horizontal line -->
|
||||||
<View
|
<View
|
||||||
android:id="@+id/stats_speed_horizontal_line"
|
android:id="@+id/stats_speed_horizontal_line"
|
||||||
@@ -123,7 +131,7 @@
|
|||||||
android:layout_marginBottom="8dp"
|
android:layout_marginBottom="8dp"
|
||||||
app:layout_constraintLeft_toRightOf="@+id/guideline"
|
app:layout_constraintLeft_toRightOf="@+id/guideline"
|
||||||
app:layout_constraintRight_toLeftOf="@+id/guideline3"
|
app:layout_constraintRight_toLeftOf="@+id/guideline3"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/stats_moving_time_value" />
|
app:layout_constraintTop_toBottomOf="@+id/stats_time_barrier" />
|
||||||
|
|
||||||
<!-- Average speed/pace -->
|
<!-- Average speed/pace -->
|
||||||
<TextView
|
<TextView
|
||||||
@@ -209,24 +217,73 @@
|
|||||||
app:layout_constraintRight_toLeftOf="@+id/guideline3"
|
app:layout_constraintRight_toLeftOf="@+id/guideline3"
|
||||||
tools:text="km/h" />
|
tools:text="km/h" />
|
||||||
|
|
||||||
|
<!-- Barrier -->
|
||||||
|
<androidx.constraintlayout.widget.Barrier
|
||||||
|
android:id="@+id/stats_speed_barrier"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:barrierDirection="bottom"
|
||||||
|
app:constraint_referenced_ids="stats_moving_speed_value,stats_max_speed_value" />
|
||||||
|
|
||||||
<!-- Horizontal line -->
|
<!-- Horizontal line -->
|
||||||
<View
|
<View
|
||||||
android:id="@+id/stats_speed_horizontal_end_line"
|
android:id="@+id/stats_elevation_horizontal_line"
|
||||||
style="@style/StatsHorizontalLine"
|
style="@style/StatsHorizontalLine"
|
||||||
android:layout_marginBottom="8dp"
|
android:layout_marginBottom="8dp"
|
||||||
app:layout_constraintLeft_toRightOf="@+id/guideline"
|
app:layout_constraintLeft_toRightOf="@+id/guideline"
|
||||||
app:layout_constraintRight_toLeftOf="@+id/guideline3"
|
app:layout_constraintRight_toLeftOf="@+id/guideline3"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/stats_max_speed_value" />
|
app:layout_constraintTop_toBottomOf="@+id/stats_speed_barrier" />
|
||||||
|
|
||||||
<LinearLayout
|
<!-- Elevation gain -->
|
||||||
android:id="@+id/linear_layout_extra"
|
<androidx.constraintlayout.widget.Group
|
||||||
android:orientation="vertical"
|
android:id="@+id/stats_elevation_group"
|
||||||
android:layout_marginBottom="16dp"
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:constraint_referenced_ids="stats_elevation_horizontal_line,stats_elevation_gain_label,stats_elevation_gain_value,stats_elevation_gain_unit" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/stats_elevation_gain_label"
|
||||||
|
style="@style/StatsSmallLabel"
|
||||||
|
android:text="@string/stats_gain"
|
||||||
|
app:layout_constrainedWidth="true"
|
||||||
|
app:layout_constraintLeft_toRightOf="@+id/guideline2"
|
||||||
|
app:layout_constraintRight_toLeftOf="@+id/guideline3"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/stats_elevation_horizontal_line" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/stats_elevation_gain_value"
|
||||||
|
style="@style/StatsSmallValue"
|
||||||
|
android:value="@string/value_unknown"
|
||||||
|
app:layout_constraintHorizontal_chainStyle="packed"
|
||||||
|
app:layout_constraintLeft_toRightOf="@+id/guideline2"
|
||||||
|
app:layout_constraintRight_toLeftOf="@+id/stats_elevation_gain_unit"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/stats_elevation_gain_label"
|
||||||
|
tools:text="100" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/stats_elevation_gain_unit"
|
||||||
|
style="@style/StatsUnit"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@+id/stats_elevation_gain_value"
|
||||||
|
app:layout_constraintLeft_toRightOf="@+id/stats_elevation_gain_value"
|
||||||
|
app:layout_constraintRight_toLeftOf="@+id/guideline3"
|
||||||
|
tools:text="m" />
|
||||||
|
|
||||||
|
<!-- Barrier -->
|
||||||
|
<androidx.constraintlayout.widget.Barrier
|
||||||
|
android:id="@+id/stats_elevation_barrier"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:barrierDirection="bottom"
|
||||||
|
app:constraint_referenced_ids="stats_elevation_gain_value" />
|
||||||
|
|
||||||
|
<!-- Horizontal line -->
|
||||||
|
<View
|
||||||
|
android:id="@+id/stats_end_horizontal_line"
|
||||||
|
style="@style/StatsHorizontalLine"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
app:layout_constraintLeft_toRightOf="@+id/guideline"
|
app:layout_constraintLeft_toRightOf="@+id/guideline"
|
||||||
app:layout_constraintRight_toLeftOf="@+id/guideline3"
|
app:layout_constraintRight_toLeftOf="@+id/guideline3"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/stats_speed_horizontal_end_line"
|
app:layout_constraintTop_toBottomOf="@+id/stats_elevation_barrier" />
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content" />
|
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
@@ -116,6 +116,14 @@
|
|||||||
app:layout_constraintTop_toBottomOf="@+id/stats_moving_time_label"
|
app:layout_constraintTop_toBottomOf="@+id/stats_moving_time_label"
|
||||||
tools:text="00:00:00" />
|
tools:text="00:00:00" />
|
||||||
|
|
||||||
|
<!-- Barrier -->
|
||||||
|
<androidx.constraintlayout.widget.Barrier
|
||||||
|
android:id="@+id/stats_time_barrier"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:barrierDirection="bottom"
|
||||||
|
app:constraint_referenced_ids="stats_total_time_value,stats_moving_time_value" />
|
||||||
|
|
||||||
<!-- Horizontal line -->
|
<!-- Horizontal line -->
|
||||||
<View
|
<View
|
||||||
android:id="@+id/stats_speed_horizontal_line"
|
android:id="@+id/stats_speed_horizontal_line"
|
||||||
@@ -123,7 +131,7 @@
|
|||||||
android:layout_marginBottom="8dp"
|
android:layout_marginBottom="8dp"
|
||||||
app:layout_constraintLeft_toRightOf="@+id/guideline"
|
app:layout_constraintLeft_toRightOf="@+id/guideline"
|
||||||
app:layout_constraintRight_toLeftOf="@+id/guideline3"
|
app:layout_constraintRight_toLeftOf="@+id/guideline3"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/stats_moving_time_value" />
|
app:layout_constraintTop_toBottomOf="@+id/stats_time_barrier" />
|
||||||
|
|
||||||
<!-- Speed/pace -->
|
<!-- Speed/pace -->
|
||||||
<TextView
|
<TextView
|
||||||
@@ -237,6 +245,14 @@
|
|||||||
app:layout_constraintRight_toLeftOf="@+id/guideline3"
|
app:layout_constraintRight_toLeftOf="@+id/guideline3"
|
||||||
tools:text="km/h" />
|
tools:text="km/h" />
|
||||||
|
|
||||||
|
<!-- Barrier -->
|
||||||
|
<androidx.constraintlayout.widget.Barrier
|
||||||
|
android:id="@+id/stats_speed_barrier"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:barrierDirection="bottom"
|
||||||
|
app:constraint_referenced_ids="stats_max_speed_value,stats_moving_speed_value" />
|
||||||
|
|
||||||
<!-- Horizontal line -->
|
<!-- Horizontal line -->
|
||||||
<View
|
<View
|
||||||
android:id="@+id/stats_elevation_horizontal_line"
|
android:id="@+id/stats_elevation_horizontal_line"
|
||||||
@@ -244,22 +260,23 @@
|
|||||||
android:layout_marginBottom="8dp"
|
android:layout_marginBottom="8dp"
|
||||||
app:layout_constraintLeft_toRightOf="@+id/guideline"
|
app:layout_constraintLeft_toRightOf="@+id/guideline"
|
||||||
app:layout_constraintRight_toLeftOf="@+id/guideline3"
|
app:layout_constraintRight_toLeftOf="@+id/guideline3"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/stats_moving_speed_value" />
|
app:layout_constraintTop_toBottomOf="@+id/stats_speed_barrier" />
|
||||||
|
|
||||||
<!-- Current elevation -->
|
<!-- Elevation -->
|
||||||
<androidx.constraintlayout.widget.Group
|
<androidx.constraintlayout.widget.Group
|
||||||
android:id="@+id/stats_elevation_current_group"
|
android:id="@+id/stats_elevation_group"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
app:constraint_referenced_ids="stats_elevation_current_label,stats_elevation_current_value,stats_elevation_current_unit,stats_elevation_horizontal_line" />
|
app:constraint_referenced_ids="stats_elevation_horizontal_line,stats_elevation_current_label,stats_elevation_current_value,stats_elevation_current_unit,stats_elevation_gain_label,stats_elevation_gain_value,stats_elevation_gain_unit" />
|
||||||
|
|
||||||
|
<!-- Elevation -->
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/stats_elevation_current_label"
|
android:id="@+id/stats_elevation_current_label"
|
||||||
style="@style/StatsSmallLabel"
|
style="@style/StatsSmallLabel"
|
||||||
android:text="@string/stats_elevation"
|
android:text="@string/stats_elevation"
|
||||||
app:layout_constrainedWidth="true"
|
app:layout_constrainedWidth="true"
|
||||||
app:layout_constraintLeft_toRightOf="@+id/guideline2"
|
app:layout_constraintLeft_toRightOf="@+id/guideline"
|
||||||
app:layout_constraintRight_toLeftOf="@+id/guideline3"
|
app:layout_constraintRight_toLeftOf="@+id/guideline2"
|
||||||
app:layout_constraintTop_toBottomOf="@id/stats_elevation_horizontal_line" />
|
app:layout_constraintTop_toBottomOf="@id/stats_elevation_horizontal_line" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
@@ -267,7 +284,7 @@
|
|||||||
style="@style/StatsSmallValue"
|
style="@style/StatsSmallValue"
|
||||||
android:value="@string/value_unknown"
|
android:value="@string/value_unknown"
|
||||||
app:layout_constraintHorizontal_chainStyle="packed"
|
app:layout_constraintHorizontal_chainStyle="packed"
|
||||||
app:layout_constraintLeft_toRightOf="@+id/guideline2"
|
app:layout_constraintLeft_toRightOf="@+id/guideline"
|
||||||
app:layout_constraintRight_toLeftOf="@+id/stats_elevation_current_unit"
|
app:layout_constraintRight_toLeftOf="@+id/stats_elevation_current_unit"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/stats_elevation_current_label"
|
app:layout_constraintTop_toBottomOf="@+id/stats_elevation_current_label"
|
||||||
tools:text="100" />
|
tools:text="100" />
|
||||||
@@ -277,6 +294,34 @@
|
|||||||
style="@style/StatsUnit"
|
style="@style/StatsUnit"
|
||||||
app:layout_constraintBottom_toBottomOf="@+id/stats_elevation_current_value"
|
app:layout_constraintBottom_toBottomOf="@+id/stats_elevation_current_value"
|
||||||
app:layout_constraintLeft_toRightOf="@+id/stats_elevation_current_value"
|
app:layout_constraintLeft_toRightOf="@+id/stats_elevation_current_value"
|
||||||
|
app:layout_constraintRight_toLeftOf="@+id/guideline2"
|
||||||
|
tools:text="m" />
|
||||||
|
|
||||||
|
<!-- Current elevation -->
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/stats_elevation_gain_label"
|
||||||
|
style="@style/StatsSmallLabel"
|
||||||
|
android:text="@string/stats_gain"
|
||||||
|
app:layout_constrainedWidth="true"
|
||||||
|
app:layout_constraintLeft_toRightOf="@+id/guideline2"
|
||||||
|
app:layout_constraintRight_toLeftOf="@+id/guideline3"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/stats_elevation_horizontal_line" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/stats_elevation_gain_value"
|
||||||
|
style="@style/StatsSmallValue"
|
||||||
|
android:value="@string/value_unknown"
|
||||||
|
app:layout_constraintHorizontal_chainStyle="packed"
|
||||||
|
app:layout_constraintLeft_toRightOf="@+id/guideline2"
|
||||||
|
app:layout_constraintRight_toLeftOf="@+id/stats_elevation_gain_unit"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/stats_elevation_gain_label"
|
||||||
|
tools:text="100" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/stats_elevation_gain_unit"
|
||||||
|
style="@style/StatsUnit"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@+id/stats_elevation_gain_value"
|
||||||
|
app:layout_constraintLeft_toRightOf="@+id/stats_elevation_gain_value"
|
||||||
app:layout_constraintRight_toLeftOf="@+id/guideline3"
|
app:layout_constraintRight_toLeftOf="@+id/guideline3"
|
||||||
tools:text="m" />
|
tools:text="m" />
|
||||||
|
|
||||||
@@ -287,6 +332,14 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
app:constraint_referenced_ids="stats_latitude_label,stats_latitude_value,stats_longitude_label,stats_longitude_value,stats_coordinate_horizontal_line" />
|
app:constraint_referenced_ids="stats_latitude_label,stats_latitude_value,stats_longitude_label,stats_longitude_value,stats_coordinate_horizontal_line" />
|
||||||
|
|
||||||
|
<!-- Barrier -->
|
||||||
|
<androidx.constraintlayout.widget.Barrier
|
||||||
|
android:id="@+id/stats_elevation_barrier"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:barrierDirection="bottom"
|
||||||
|
app:constraint_referenced_ids="stats_elevation_current_value,stats_elevation_gain_value" />
|
||||||
|
|
||||||
<!-- Horizontal line -->
|
<!-- Horizontal line -->
|
||||||
<View
|
<View
|
||||||
android:id="@+id/stats_coordinate_horizontal_line"
|
android:id="@+id/stats_coordinate_horizontal_line"
|
||||||
@@ -294,7 +347,7 @@
|
|||||||
android:layout_marginBottom="8dp"
|
android:layout_marginBottom="8dp"
|
||||||
app:layout_constraintLeft_toRightOf="@+id/guideline"
|
app:layout_constraintLeft_toRightOf="@+id/guideline"
|
||||||
app:layout_constraintRight_toLeftOf="@+id/guideline3"
|
app:layout_constraintRight_toLeftOf="@+id/guideline3"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/stats_elevation_current_value" />
|
app:layout_constraintTop_toBottomOf="@+id/stats_elevation_barrier" />
|
||||||
|
|
||||||
<!-- Latitude -->
|
<!-- Latitude -->
|
||||||
<TextView
|
<TextView
|
||||||
@@ -333,6 +386,14 @@
|
|||||||
app:layout_constraintTop_toBottomOf="@+id/stats_longitude_label"
|
app:layout_constraintTop_toBottomOf="@+id/stats_longitude_label"
|
||||||
tools:text="0.834937848" />
|
tools:text="0.834937848" />
|
||||||
|
|
||||||
|
<!-- Barrier -->
|
||||||
|
<androidx.constraintlayout.widget.Barrier
|
||||||
|
android:id="@+id/stats_location_barrier"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:barrierDirection="bottom"
|
||||||
|
app:constraint_referenced_ids="stats_latitude_value,stats_longitude_value" />
|
||||||
|
|
||||||
<!-- Horizontal line -->
|
<!-- Horizontal line -->
|
||||||
<View
|
<View
|
||||||
android:id="@+id/stats_sensor_horizontal_line"
|
android:id="@+id/stats_sensor_horizontal_line"
|
||||||
@@ -340,7 +401,7 @@
|
|||||||
android:layout_marginBottom="8dp"
|
android:layout_marginBottom="8dp"
|
||||||
app:layout_constraintLeft_toRightOf="@+id/guideline"
|
app:layout_constraintLeft_toRightOf="@+id/guideline"
|
||||||
app:layout_constraintRight_toLeftOf="@+id/guideline3"
|
app:layout_constraintRight_toLeftOf="@+id/guideline3"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/stats_latitude_value" />
|
app:layout_constraintTop_toBottomOf="@+id/stats_location_barrier" />
|
||||||
|
|
||||||
<!-- Heart rate -->
|
<!-- Heart rate -->
|
||||||
<androidx.constraintlayout.widget.Group
|
<androidx.constraintlayout.widget.Group
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
<bool name="stats_show_coordinate_default" translatable="false">false</bool>
|
<bool name="stats_show_coordinate_default" translatable="false">false</bool>
|
||||||
|
|
||||||
<string name="stats_show_grade_elevation_key" translatable="false">statsShowGradeElevation</string>
|
<string name="stats_show_grade_elevation_key" translatable="false">statsShowGradeElevation</string>
|
||||||
<bool name="stats_show_elevation_default" translatable="false">false</bool>
|
<bool name="stats_show_elevation_default" translatable="false">true</bool>
|
||||||
|
|
||||||
<string name="stats_show_on_lockscreen_while_recording_key" translatable="false">trackdetail_show_on_lockscreen_while_recording</string>
|
<string name="stats_show_on_lockscreen_while_recording_key" translatable="false">trackdetail_show_on_lockscreen_while_recording</string>
|
||||||
<bool name="stats_show_on_lockscreen_while_recording_default" translatable="false">false</bool>
|
<bool name="stats_show_on_lockscreen_while_recording_default" translatable="false">false</bool>
|
||||||
|
|||||||
Reference in New Issue
Block a user