forked from upstream-mirrors/OpenTracks
Implement elevation gain via barometer.
This commit is contained in:
+34
@@ -0,0 +1,34 @@
|
||||
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() {
|
||||
// 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(-1.08, elevationSumManager.getElevationLoss_m(), 0.01);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
// 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, 1015.14f, 1015.15f};
|
||||
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) {
|
||||
if (elevationChange.getElevationChange_m() > 0) {
|
||||
elevationGain_m += elevationChange.getElevationChange_m();
|
||||
} else {
|
||||
elevationLoss_m += elevationChange.getElevationChange_m();
|
||||
}
|
||||
lastUsedPressureValue_hPa = elevationChange.getCurrentSensorValue_hPa();
|
||||
}
|
||||
}
|
||||
|
||||
// then
|
||||
assertEquals(0f, elevationGain_m, 0.01);
|
||||
assertEquals(-1.08f, elevationLoss_m, 0.01);
|
||||
assertEquals(1015.13, lastUsedPressureValue_hPa, 0.01);
|
||||
}
|
||||
}
|
||||
@@ -32,6 +32,7 @@ public class TrackPoint {
|
||||
private Float heartRate_bpm = null;
|
||||
private Float cyclingCadence_rpm = null;
|
||||
private Float power = null;
|
||||
private float elevationGain = Float.NaN;
|
||||
|
||||
public TrackPoint() {
|
||||
this.location = new Location("");
|
||||
@@ -92,6 +93,18 @@ public class TrackPoint {
|
||||
return location;
|
||||
}
|
||||
|
||||
public boolean hasElevationGain() {
|
||||
return !Float.isNaN(elevationGain);
|
||||
}
|
||||
|
||||
public float getElevationGain() {
|
||||
return elevationGain;
|
||||
}
|
||||
|
||||
public void setElevationGain(float elevationGain) {
|
||||
this.elevationGain = elevationGain;
|
||||
}
|
||||
|
||||
public double getLatitude() {
|
||||
return location.getLatitude();
|
||||
}
|
||||
|
||||
@@ -87,6 +87,10 @@ public class StatisticsRecordedFragment extends Fragment {
|
||||
private TextView speedMovingValue;
|
||||
private TextView speedMovingUnit;
|
||||
|
||||
private TextView elevationCurrentGroup;
|
||||
private TextView elevationTotalGainValue;
|
||||
private TextView elevationTotalGainUnit;
|
||||
|
||||
public static StatisticsRecordedFragment newInstance(Track.Id trackId) {
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putParcelable(TRACK_ID_KEY, trackId);
|
||||
@@ -130,6 +134,11 @@ public class StatisticsRecordedFragment extends Fragment {
|
||||
speedMovingLabel = view.findViewById(R.id.stats_moving_speed_label);
|
||||
speedMovingValue = view.findViewById(R.id.stats_moving_speed_value);
|
||||
speedMovingUnit = view.findViewById(R.id.stats_moving_speed_unit);
|
||||
|
||||
elevationCurrentGroup = view.findViewById(R.id.stats_elevation_current_group);
|
||||
|
||||
elevationTotalGainValue = view.findViewById(R.id.stats_elevation_gain_value);
|
||||
elevationTotalGainUnit = view.findViewById(R.id.stats_elevation_gain_unit);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -187,6 +196,9 @@ public class StatisticsRecordedFragment extends Fragment {
|
||||
speedMovingLabel = null;
|
||||
speedMovingValue = null;
|
||||
speedMovingUnit = null;
|
||||
|
||||
elevationTotalGainValue = null;
|
||||
elevationTotalGainUnit = null;
|
||||
}
|
||||
|
||||
public void loadStatistics() {
|
||||
@@ -264,5 +276,14 @@ public class StatisticsRecordedFragment extends Fragment {
|
||||
speedMovingValue.setText(parts.first);
|
||||
speedMovingUnit.setText(parts.second);
|
||||
}
|
||||
|
||||
// Set elevation gain
|
||||
{
|
||||
double elevationGain_m = trackStatistics != null ? trackStatistics.getTotalElevationGain() : 0f;
|
||||
|
||||
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 speedMovingValue;
|
||||
private TextView speedMovingUnit;
|
||||
private Group elevationGroup;
|
||||
private TextView speedLabel;
|
||||
private TextView speedValue;
|
||||
private TextView speedUnit;
|
||||
private TextView elevationValue;
|
||||
private TextView elevationUnit;
|
||||
private Group elevationCurrentGroup;
|
||||
private TextView elevationCurrentValue;
|
||||
private TextView elevationCurrentUnit;
|
||||
private TextView elevationTotalGainValue;
|
||||
private TextView elevationTotalGainUnit;
|
||||
private Group coordinateGroup;
|
||||
private TextView latitudeValue;
|
||||
private TextView longitudeValue;
|
||||
@@ -178,14 +180,16 @@ public class StatisticsRecordingFragment extends Fragment implements TrackDataLi
|
||||
speedMovingValue = view.findViewById(R.id.stats_moving_speed_value);
|
||||
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);
|
||||
speedValue = view.findViewById(R.id.stats_speed_value);
|
||||
speedUnit = view.findViewById(R.id.stats_speed_unit);
|
||||
|
||||
elevationValue = view.findViewById(R.id.stats_elevation_current_value);
|
||||
elevationUnit = view.findViewById(R.id.stats_elevation_current_unit);
|
||||
elevationCurrentGroup = view.findViewById(R.id.stats_elevation_current_group);
|
||||
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);
|
||||
|
||||
@@ -276,14 +280,16 @@ public class StatisticsRecordingFragment extends Fragment implements TrackDataLi
|
||||
speedMovingValue = null;
|
||||
speedMovingUnit = null;
|
||||
|
||||
elevationGroup = null;
|
||||
|
||||
speedLabel = null;
|
||||
speedValue = null;
|
||||
speedUnit = null;
|
||||
|
||||
elevationValue = null;
|
||||
elevationUnit = null;
|
||||
elevationCurrentGroup = null;
|
||||
elevationCurrentValue = null;
|
||||
elevationCurrentUnit = null;
|
||||
|
||||
elevationTotalGainValue = null;
|
||||
elevationTotalGainUnit = 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}.
|
||||
* Also sets elevation gain.
|
||||
*/
|
||||
private void updateSensorDataUI() {
|
||||
TrackRecordingServiceInterface trackRecordingService = trackRecordingServiceConnection.getServiceIfBound();
|
||||
|
||||
SensorDataSet sensorDataSet = null;
|
||||
Float elevationGain_m = null;
|
||||
if (trackRecordingService == null) {
|
||||
Log.d(TAG, "Cannot get the track recording service.");
|
||||
} else {
|
||||
sensorDataSet = trackRecordingService.getSensorData();
|
||||
elevationGain_m = trackRecordingService.getElevationGain_m();
|
||||
}
|
||||
|
||||
setHeartRateSensorData(sensorDataSet);
|
||||
setCadenceSensorData(sensorDataSet);
|
||||
setSpeedSensorData(sensorDataSet, isSelectedTrackRecording());
|
||||
|
||||
setElevationGainFromSensor(elevationGain_m);
|
||||
}
|
||||
|
||||
private void setHeartRateSensorData(SensorDataSet sensorDataSet) {
|
||||
@@ -468,6 +479,33 @@ public class StatisticsRecordingFragment extends Fragment implements TrackDataLi
|
||||
cadenceValueView.setText(sensorValue);
|
||||
}
|
||||
|
||||
// Set elevation gain
|
||||
public void setTotalElevationGain(Float elevationGain_m) {
|
||||
boolean metricUnits = PreferencesUtils.isMetricUnits(getContext());
|
||||
|
||||
if (elevationGain_m == null) {
|
||||
elevationGain_m = 0f;
|
||||
}
|
||||
if (lastTrackStatistics != null) {
|
||||
elevationGain_m += (float) lastTrackStatistics.getTotalElevationGain();
|
||||
}
|
||||
|
||||
Pair<String, String> parts = StringUtils.formatElevation(getContext(), elevationGain_m, metricUnits);
|
||||
elevationTotalGainValue.setText(parts.first);
|
||||
elevationTotalGainUnit.setText(parts.second);
|
||||
}
|
||||
|
||||
public void setElevationGainFromSensor(Float elevationGain_m) {
|
||||
float totalElevationGain = 0;
|
||||
if (elevationGain_m != null) {
|
||||
totalElevationGain = elevationGain_m;
|
||||
}
|
||||
if (lastTrackStatistics != null) {
|
||||
totalElevationGain += (float) lastTrackStatistics.getTotalElevationGain();
|
||||
}
|
||||
setTotalElevationGain(totalElevationGain);
|
||||
}
|
||||
|
||||
/**
|
||||
* If cadence and hear rate groups are invisible then sensor horizontal line hast to be invisible too.
|
||||
*/
|
||||
@@ -475,7 +513,6 @@ public class StatisticsRecordingFragment extends Fragment implements TrackDataLi
|
||||
if (cadenceGroup.getVisibility() != View.VISIBLE && heartRateGroup.getVisibility() != View.VISIBLE) {
|
||||
sensorHorizontalLine.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void setSpeedSensorData(SensorDataSet sensorDataSet, boolean isRecording) {
|
||||
@@ -555,10 +592,11 @@ public class StatisticsRecordingFragment extends Fragment implements TrackDataLi
|
||||
speedMovingUnit.setText(parts.second);
|
||||
}
|
||||
|
||||
// Make elevation visible?
|
||||
// Set elevation (gain)
|
||||
{
|
||||
// Make elevation visible?
|
||||
boolean showElevation = PreferencesUtils.isShowStatsElevation(getContext());
|
||||
elevationGroup.setVisibility(showElevation ? View.VISIBLE : View.GONE);
|
||||
elevationCurrentGroup.setVisibility(showElevation ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -594,14 +632,14 @@ public class StatisticsRecordingFragment extends Fragment implements TrackDataLi
|
||||
|
||||
// Set elevation
|
||||
boolean showElevation = PreferencesUtils.isShowStatsElevation(getContext());
|
||||
elevationGroup.setVisibility(showElevation ? View.VISIBLE : View.GONE);
|
||||
elevationCurrentGroup.setVisibility(showElevation ? View.VISIBLE : View.GONE);
|
||||
|
||||
if (showElevation) {
|
||||
// Current elevation
|
||||
double altitude = lastTrackPoint != null && lastTrackPoint.hasAltitude() ? lastTrackPoint.getAltitude() : Double.NaN;
|
||||
Pair<String, String> parts = StringUtils.formatElevation(getContext(), altitude, metricUnits);
|
||||
|
||||
elevationValue.setText(parts.first);
|
||||
elevationUnit.setText(parts.second);
|
||||
elevationCurrentValue.setText(parts.first);
|
||||
elevationCurrentUnit.setText(parts.second);
|
||||
}
|
||||
|
||||
// Set coordinate
|
||||
|
||||
@@ -49,6 +49,7 @@ import de.dennisguse.opentracks.content.sensor.SensorDataSet;
|
||||
import de.dennisguse.opentracks.services.handlers.GpsStatusValue;
|
||||
import de.dennisguse.opentracks.services.handlers.HandlerServer;
|
||||
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.PeriodicTaskExecutor;
|
||||
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:
|
||||
private WakeLock wakeLock;
|
||||
private BluetoothRemoteSensorManager remoteSensorManager;
|
||||
private ElevationSumManager elevationSumManager;
|
||||
|
||||
private TrackStatisticsUpdater trackStatisticsUpdater;
|
||||
private TrackPoint lastTrackPoint;
|
||||
@@ -174,6 +176,11 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
|
||||
remoteSensorManager = null;
|
||||
}
|
||||
|
||||
if (elevationSumManager != null) {
|
||||
elevationSumManager.stop(this);
|
||||
elevationSumManager = null;
|
||||
}
|
||||
|
||||
// Reverse order from onCreate
|
||||
showNotification(false); //TODO Why?
|
||||
|
||||
@@ -371,6 +378,10 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
|
||||
// Update instance variables
|
||||
remoteSensorManager = new BluetoothRemoteSensorManager(this);
|
||||
remoteSensorManager.start();
|
||||
|
||||
elevationSumManager = new ElevationSumManager();
|
||||
elevationSumManager.start(this);
|
||||
|
||||
lastTrackPoint = null;
|
||||
isIdle = false;
|
||||
|
||||
@@ -456,6 +467,11 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
|
||||
remoteSensorManager.stop();
|
||||
remoteSensorManager = null;
|
||||
}
|
||||
if (elevationSumManager != null) {
|
||||
elevationSumManager.stop(this);
|
||||
elevationSumManager = null;
|
||||
}
|
||||
|
||||
lastTrackPoint = null;
|
||||
|
||||
handlerServer.stop(this);
|
||||
@@ -533,6 +549,10 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
|
||||
//TODO Figure out how to avoid loading the lastValidTrackPoint from the database
|
||||
TrackPoint lastValidTrackPoint = getLastValidTrackPointInCurrentSegment(track.getId());
|
||||
|
||||
if (elevationSumManager != null) {
|
||||
trackPoint.setElevationGain(elevationSumManager.getElevationGain_m());
|
||||
}
|
||||
|
||||
//Storing trackPoint
|
||||
|
||||
// Always insert the first segment location
|
||||
@@ -556,15 +576,19 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
|
||||
insertTrackPoint(track, TrackPoint.createPause());
|
||||
|
||||
insertTrackPoint(track, trackPoint);
|
||||
isIdle = false;
|
||||
elevationSumManager.reset();
|
||||
|
||||
isIdle = false;
|
||||
lastTrackPoint = trackPoint;
|
||||
return;
|
||||
}
|
||||
|
||||
if (trackPoint.hasSensorData() || distanceToLastTrackLocation >= recordingDistanceInterval) {
|
||||
insertTrackPointIfNewer(track, lastTrackPoint);
|
||||
|
||||
insertTrackPoint(track, trackPoint);
|
||||
elevationSumManager.reset();
|
||||
|
||||
isIdle = false;
|
||||
|
||||
lastTrackPoint = trackPoint;
|
||||
@@ -573,7 +597,10 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
|
||||
|
||||
if (!isIdle && !TrackPointUtils.isMoving(trackPoint)) {
|
||||
insertTrackPointIfNewer(track, lastTrackPoint);
|
||||
|
||||
insertTrackPoint(track, trackPoint);
|
||||
elevationSumManager.reset();
|
||||
|
||||
isIdle = true;
|
||||
|
||||
lastTrackPoint = trackPoint;
|
||||
@@ -582,7 +609,10 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
|
||||
|
||||
if (isIdle && TrackPointUtils.isMoving(trackPoint)) {
|
||||
insertTrackPointIfNewer(track, lastTrackPoint);
|
||||
|
||||
insertTrackPoint(track, trackPoint);
|
||||
elevationSumManager.reset();
|
||||
|
||||
isIdle = false;
|
||||
|
||||
lastTrackPoint = trackPoint;
|
||||
@@ -672,6 +702,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) {
|
||||
if (isRecording()) {
|
||||
Intent intent = IntentUtils.newIntent(this, TrackRecordingActivity.class)
|
||||
|
||||
@@ -99,6 +99,11 @@ class TrackRecordingServiceBinder extends android.os.Binder implements TrackReco
|
||||
return trackRecordingService.getSensorDataSet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Float getElevationGain_m() {
|
||||
return trackRecordingService.getElevationGain_m();
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
@Override
|
||||
public void setRemoteSensorManager(BluetoothRemoteSensorManager remoteSensorManager) {
|
||||
|
||||
@@ -65,12 +65,19 @@ public interface TrackRecordingServiceInterface {
|
||||
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();
|
||||
|
||||
/**
|
||||
* Gets the current elevation gain.
|
||||
*
|
||||
* @return elevation gain in m or null.
|
||||
*/
|
||||
Float getElevationGain_m();
|
||||
|
||||
@VisibleForTesting
|
||||
void setRemoteSensorManager(BluetoothRemoteSensorManager remoteSensorManager);
|
||||
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
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.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 lastUsedPressureValue_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;
|
||||
}
|
||||
|
||||
isConnected = sensorManager.registerListener(this, pressureSensor, SAMPLING_RATE);
|
||||
lastUsedPressureValue_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;
|
||||
}
|
||||
|
||||
public float getElevationGain_m() {
|
||||
return elevationGain_m;
|
||||
}
|
||||
|
||||
public float getElevationLoss_m() {
|
||||
return elevationLoss_m;
|
||||
}
|
||||
|
||||
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) {
|
||||
onSensorValueChanged(event.values[0]);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void onSensorValueChanged(float value_hPa) {
|
||||
if (Float.isNaN(lastUsedPressureValue_hPa)) {
|
||||
lastUsedPressureValue_hPa = value_hPa;
|
||||
return;
|
||||
}
|
||||
|
||||
PressureSensorUtils.ElevationChange elevationChange = PressureSensorUtils.computeChanges_m(lastUsedPressureValue_hPa, value_hPa);
|
||||
if (elevationChange != null) {
|
||||
if (elevationChange.getElevationChange_m() > 0) {
|
||||
elevationGain_m += elevationChange.getElevationChange_m();
|
||||
} else {
|
||||
elevationLoss_m += elevationChange.getElevationChange_m();
|
||||
}
|
||||
lastUsedPressureValue_hPa = elevationChange.getCurrentSensorValue_hPa();
|
||||
}
|
||||
|
||||
Log.v(TAG, "elevation gain: " + elevationGain_m);
|
||||
}
|
||||
}
|
||||
@@ -228,7 +228,7 @@ public class TrackStatistics {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the total elevation gain in meters. This is calculated as the sum of all positive differences in the smoothed elevation.
|
||||
* Gets the total elevation gain in meters.
|
||||
*/
|
||||
public double getTotalElevationGain() {
|
||||
return totalElevationGain_m;
|
||||
|
||||
@@ -112,7 +112,7 @@ public class TrackStatisticsUpdater {
|
||||
* Adds a trackPoint.
|
||||
* TODO: This assume trackPoint has a valid time.
|
||||
*
|
||||
* @param trackPoint the trackPoint
|
||||
* @param trackPoint the trackPoint
|
||||
* @param minRecordingDistance the min recording distance
|
||||
*/
|
||||
public void addTrackPoint(TrackPoint trackPoint, int minRecordingDistance) {
|
||||
@@ -134,8 +134,16 @@ public class TrackStatisticsUpdater {
|
||||
return;
|
||||
}
|
||||
|
||||
//TODO Use Barometer to compute elevation gain.
|
||||
double elevationDifference = trackPoint.hasAltitude() ? updateElevation(trackPoint.getAltitude()) : 0.0;
|
||||
//Update absolute (GPS-based) elevation
|
||||
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) {
|
||||
lastTrackPoint = trackPoint;
|
||||
@@ -207,23 +215,21 @@ public class TrackStatisticsUpdater {
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates an elevation reading. Returns the difference.
|
||||
* Updates an elevation reading.
|
||||
*
|
||||
* @param elevation the elevation
|
||||
* @return the difference
|
||||
*/
|
||||
@VisibleForTesting
|
||||
private double updateElevation(double elevation) {
|
||||
private double updateAbsoluteElevation(double elevation) {
|
||||
// Update elevation using the smoothed average
|
||||
double oldAverage = elevationBuffer_m.getAverage();
|
||||
elevationBuffer_m.setNext(elevation);
|
||||
double newAverage = elevationBuffer_m.getAverage();
|
||||
|
||||
currentSegment.updateElevationExtremities(newAverage);
|
||||
double difference = newAverage - oldAverage;
|
||||
if (difference > 0) {
|
||||
currentSegment.addTotalElevationGain(difference);
|
||||
}
|
||||
return difference;
|
||||
|
||||
return newAverage - oldAverage;
|
||||
}
|
||||
|
||||
private TrackStatistics init(long time) {
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
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 = 5.0f;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the elevation gain and elevation loss.
|
||||
*
|
||||
* @return null if no meaningful elevation change occurred.
|
||||
*/
|
||||
public static ElevationChange computeChanges_m(float lastSensorValue_hPa, float currentSensorValue_hPa) {
|
||||
float lastSensorValue_m = SensorManager.getAltitude(SensorManager.PRESSURE_STANDARD_ATMOSPHERE, lastSensorValue_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;
|
||||
}
|
||||
|
||||
return new ElevationChange(currentSensorValue_hPa, elevationChange_m);
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,8 @@ public class UnitConversions {
|
||||
public static final long S_TO_MS = 1000;
|
||||
|
||||
// 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_MINUTE_MS = (long) (UnitConversions.MIN_TO_S * UnitConversions.S_TO_MS);
|
||||
public static final long ONE_HOUR_MS = 60 * ONE_MINUTE_MS;
|
||||
|
||||
@@ -209,6 +209,48 @@
|
||||
app:layout_constraintRight_toLeftOf="@+id/guideline3"
|
||||
tools:text="km/h" />
|
||||
|
||||
<!-- Elevation gain -->
|
||||
<View
|
||||
android:id="@+id/stats_elevation_horizontal_line"
|
||||
style="@style/StatsHorizontalLine"
|
||||
android:layout_marginBottom="8dp"
|
||||
app:layout_constraintLeft_toRightOf="@+id/guideline"
|
||||
app:layout_constraintRight_toLeftOf="@+id/guideline3"
|
||||
app:layout_constraintTop_toBottomOf="@+id/stats_moving_speed_value" />
|
||||
|
||||
<androidx.constraintlayout.widget.Group
|
||||
android:id="@+id/stats_elevation_gain_group"
|
||||
android:layout_width="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" />
|
||||
|
||||
<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" />
|
||||
|
||||
<!-- Horizontal line -->
|
||||
<View
|
||||
android:id="@+id/stats_speed_horizontal_end_line"
|
||||
|
||||
@@ -258,8 +258,8 @@
|
||||
style="@style/StatsSmallLabel"
|
||||
android:text="@string/stats_elevation"
|
||||
app:layout_constrainedWidth="true"
|
||||
app:layout_constraintLeft_toRightOf="@+id/guideline2"
|
||||
app:layout_constraintRight_toLeftOf="@+id/guideline3"
|
||||
app:layout_constraintLeft_toRightOf="@+id/guideline"
|
||||
app:layout_constraintRight_toLeftOf="@+id/guideline2"
|
||||
app:layout_constraintTop_toBottomOf="@id/stats_elevation_horizontal_line" />
|
||||
|
||||
<TextView
|
||||
@@ -267,7 +267,7 @@
|
||||
style="@style/StatsSmallValue"
|
||||
android:value="@string/value_unknown"
|
||||
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_constraintTop_toBottomOf="@+id/stats_elevation_current_label"
|
||||
tools:text="100" />
|
||||
@@ -277,6 +277,40 @@
|
||||
style="@style/StatsUnit"
|
||||
app:layout_constraintBottom_toBottomOf="@+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 -->
|
||||
<androidx.constraintlayout.widget.Group
|
||||
android:id="@+id/stats_elevation_gain_group"
|
||||
android:layout_width="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" />
|
||||
|
||||
<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" />
|
||||
|
||||
|
||||
Reference in New Issue
Block a user