forked from upstream-mirrors/OpenTracks
TrackStatistics and ChartView: handle altitude and speed as optional with null as default value.
Then only the relevant part of the y-axis is shown. Fixes #795.
This commit is contained in:
@@ -1,5 +1,8 @@
|
|||||||
package de.dennisguse.opentracks.chart;
|
package de.dennisguse.opentracks.chart;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertNull;
|
||||||
|
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
@@ -7,15 +10,13 @@ import org.junit.runner.RunWith;
|
|||||||
|
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
|
|
||||||
|
import de.dennisguse.opentracks.content.data.Altitude;
|
||||||
import de.dennisguse.opentracks.content.data.Distance;
|
import de.dennisguse.opentracks.content.data.Distance;
|
||||||
import de.dennisguse.opentracks.content.data.Speed;
|
import de.dennisguse.opentracks.content.data.Speed;
|
||||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||||
import de.dennisguse.opentracks.fragments.TrackStubUtils;
|
import de.dennisguse.opentracks.fragments.TrackStubUtils;
|
||||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
|
||||||
import static org.junit.Assert.assertNull;
|
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4.class)
|
@RunWith(AndroidJUnit4.class)
|
||||||
public class ChartPointTest {
|
public class ChartPointTest {
|
||||||
|
|
||||||
@@ -26,7 +27,7 @@ public class ChartPointTest {
|
|||||||
statistics.setTotalTime(Duration.ofSeconds(1000));
|
statistics.setTotalTime(Duration.ofSeconds(1000));
|
||||||
|
|
||||||
// when
|
// when
|
||||||
ChartPoint point = new ChartPoint(statistics, TrackStubUtils.createDefaultTrackPoint(), Speed.of(0), 0, false, false);
|
ChartPoint point = new ChartPoint(statistics, TrackStubUtils.createDefaultTrackPoint(), Speed.of(0), Altitude.EGM2008.of(0), false, false);
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertEquals(1000000, (long) point.getTimeOrDistance());
|
assertEquals(1000000, (long) point.getTimeOrDistance());
|
||||||
@@ -39,7 +40,7 @@ public class ChartPointTest {
|
|||||||
statistics.setTotalDistance(Distance.of(1000));
|
statistics.setTotalDistance(Distance.of(1000));
|
||||||
|
|
||||||
// when
|
// when
|
||||||
ChartPoint point = new ChartPoint(statistics, TrackStubUtils.createDefaultTrackPoint(), Speed.of(0), 0, true, true);
|
ChartPoint point = new ChartPoint(statistics, TrackStubUtils.createDefaultTrackPoint(), Speed.of(0), Altitude.EGM2008.of(0), true, true);
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertEquals(1, (long) point.getTimeOrDistance());
|
assertEquals(1, (long) point.getTimeOrDistance());
|
||||||
@@ -51,7 +52,7 @@ public class ChartPointTest {
|
|||||||
TrackStatistics statistics = new TrackStatistics();
|
TrackStatistics statistics = new TrackStatistics();
|
||||||
|
|
||||||
// when
|
// when
|
||||||
ChartPoint point = new ChartPoint(statistics, TrackStubUtils.createDefaultTrackPoint(), Speed.of(10), 50, false, true);
|
ChartPoint point = new ChartPoint(statistics, TrackStubUtils.createDefaultTrackPoint(), Speed.of(10), Altitude.EGM2008.of(50), false, true);
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertEquals(50, point.getAltitude(), 0.01);
|
assertEquals(50, point.getAltitude(), 0.01);
|
||||||
@@ -65,7 +66,7 @@ public class ChartPointTest {
|
|||||||
TrackStatistics statistics = new TrackStatistics();
|
TrackStatistics statistics = new TrackStatistics();
|
||||||
|
|
||||||
// when
|
// when
|
||||||
ChartPoint point = new ChartPoint(statistics, TrackStubUtils.createDefaultTrackPoint(), Speed.of(10), 50, false, true);
|
ChartPoint point = new ChartPoint(statistics, TrackStubUtils.createDefaultTrackPoint(), Speed.of(10), Altitude.EGM2008.of(50), false, true);
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertNull(point.getHeartRate());
|
assertNull(point.getHeartRate());
|
||||||
@@ -84,7 +85,7 @@ public class ChartPointTest {
|
|||||||
TrackStatistics statistics = new TrackStatistics();
|
TrackStatistics statistics = new TrackStatistics();
|
||||||
|
|
||||||
// when
|
// when
|
||||||
ChartPoint point = new ChartPoint(statistics, trackPoint, Speed.of(10), 50, false, true);
|
ChartPoint point = new ChartPoint(statistics, trackPoint, Speed.of(10), Altitude.EGM2008.of(50), false, true);
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertEquals(100.0, point.getHeartRate(), 0.01);
|
assertEquals(100.0, point.getHeartRate(), 0.01);
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package de.dennisguse.opentracks.chart;
|
|||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.VisibleForTesting;
|
import androidx.annotation.VisibleForTesting;
|
||||||
|
|
||||||
|
import de.dennisguse.opentracks.content.data.Altitude;
|
||||||
import de.dennisguse.opentracks.content.data.Distance;
|
import de.dennisguse.opentracks.content.data.Distance;
|
||||||
import de.dennisguse.opentracks.content.data.Speed;
|
import de.dennisguse.opentracks.content.data.Speed;
|
||||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||||
@@ -14,7 +15,7 @@ public class ChartPoint {
|
|||||||
private double timeOrDistance;
|
private double timeOrDistance;
|
||||||
|
|
||||||
//Y-axis
|
//Y-axis
|
||||||
private final double altitude;
|
private Double altitude;
|
||||||
private Double speed;
|
private Double speed;
|
||||||
private Double pace;
|
private Double pace;
|
||||||
private Double heartRate;
|
private Double heartRate;
|
||||||
@@ -27,17 +28,21 @@ public class ChartPoint {
|
|||||||
this.altitude = altitude;
|
this.altitude = altitude;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ChartPoint(@NonNull TrackStatistics trackStatistics, @NonNull TrackPoint trackPoint, Speed smoothedSpeed, double smoothedAltitude_m, boolean chartByDistance, boolean metricUnits) {
|
public ChartPoint(@NonNull TrackStatistics trackStatistics, @NonNull TrackPoint trackPoint, Speed smoothedSpeed, Altitude smoothedAltitude, boolean chartByDistance, boolean metricUnits) {
|
||||||
if (chartByDistance) {
|
if (chartByDistance) {
|
||||||
timeOrDistance = trackStatistics.getTotalDistance().toKM_Miles(metricUnits);
|
timeOrDistance = trackStatistics.getTotalDistance().toKM_Miles(metricUnits);
|
||||||
} else {
|
} else {
|
||||||
timeOrDistance = trackStatistics.getTotalTime().toMillis();
|
timeOrDistance = trackStatistics.getTotalTime().toMillis();
|
||||||
}
|
}
|
||||||
|
|
||||||
altitude = Distance.of(smoothedAltitude_m).toM_FT(metricUnits);
|
if (smoothedAltitude != null) {
|
||||||
|
altitude = Distance.of(smoothedAltitude.toM()).toM_FT(metricUnits);
|
||||||
|
}
|
||||||
|
|
||||||
speed = smoothedSpeed.to(metricUnits);
|
if (smoothedSpeed != null) {
|
||||||
pace = smoothedSpeed.toPace(metricUnits).toMillis() * UnitConversions.MS_TO_S * UnitConversions.S_TO_MIN;
|
speed = smoothedSpeed.to(metricUnits);
|
||||||
|
pace = smoothedSpeed.toPace(metricUnits).toMillis() * UnitConversions.MS_TO_S * UnitConversions.S_TO_MIN;
|
||||||
|
}
|
||||||
if (trackPoint.hasHeartRate()) {
|
if (trackPoint.hasHeartRate()) {
|
||||||
heartRate = (double) trackPoint.getHeartRate_bpm();
|
heartRate = (double) trackPoint.getHeartRate_bpm();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,7 +41,6 @@ import androidx.core.view.GestureDetectorCompat;
|
|||||||
import java.text.NumberFormat;
|
import java.text.NumberFormat;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import de.dennisguse.opentracks.MarkerDetailActivity;
|
import de.dennisguse.opentracks.MarkerDetailActivity;
|
||||||
@@ -873,9 +872,9 @@ public class ChartView extends View {
|
|||||||
boolean drawFirstPoint = false;
|
boolean drawFirstPoint = false;
|
||||||
path.rewind();
|
path.rewind();
|
||||||
|
|
||||||
Iterator<ChartPoint> iterator = chartPoints.iterator();
|
Integer finalX = null;
|
||||||
while (iterator.hasNext()) {
|
|
||||||
ChartPoint point = iterator.next();
|
for (ChartPoint point : chartPoints) {
|
||||||
if (!series.isChartPointValid(point)) {
|
if (!series.isChartPointValid(point)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -893,10 +892,12 @@ public class ChartView extends View {
|
|||||||
// draw graph
|
// draw graph
|
||||||
path.lineTo(x, y);
|
path.lineTo(x, y);
|
||||||
|
|
||||||
// last point: move to lower right
|
finalX = x;
|
||||||
if (!iterator.hasNext()) {
|
}
|
||||||
path.lineTo(x, yCorner);
|
|
||||||
}
|
// last point: move to lower right
|
||||||
|
if (finalX != null) {
|
||||||
|
path.lineTo(finalX, yCorner);
|
||||||
}
|
}
|
||||||
|
|
||||||
// back to lower left corner
|
// back to lower left corner
|
||||||
|
|||||||
@@ -17,7 +17,9 @@
|
|||||||
package de.dennisguse.opentracks.content;
|
package de.dennisguse.opentracks.content;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import de.dennisguse.opentracks.content.data.Altitude;
|
||||||
import de.dennisguse.opentracks.content.data.Marker;
|
import de.dennisguse.opentracks.content.data.Marker;
|
||||||
import de.dennisguse.opentracks.content.data.Speed;
|
import de.dennisguse.opentracks.content.data.Speed;
|
||||||
import de.dennisguse.opentracks.content.data.Track;
|
import de.dennisguse.opentracks.content.data.Track;
|
||||||
@@ -49,7 +51,7 @@ public interface TrackDataListener {
|
|||||||
*
|
*
|
||||||
* @param trackPoint the trackPoint
|
* @param trackPoint the trackPoint
|
||||||
*/
|
*/
|
||||||
default void onSampledInTrackPoint(@NonNull TrackPoint trackPoint, @NonNull TrackStatistics trackStatistics, Speed smoothedSpeed, double smoothedAltitude_m) {
|
default void onSampledInTrackPoint(@NonNull TrackPoint trackPoint, @NonNull TrackStatistics trackStatistics, Speed smoothedSpeed, @Nullable Altitude smoothedAltitude_m) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ public abstract class Altitude {
|
|||||||
return altitude_m;
|
return altitude_m;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public abstract Altitude replace(double altitude_m);
|
||||||
|
|
||||||
public abstract int getLabelId();
|
public abstract int getLabelId();
|
||||||
|
|
||||||
public static class WGS84 extends Altitude {
|
public static class WGS84 extends Altitude {
|
||||||
@@ -30,6 +32,11 @@ public abstract class Altitude {
|
|||||||
public static Altitude of(double altitude_m) {
|
public static Altitude of(double altitude_m) {
|
||||||
return new WGS84(altitude_m);
|
return new WGS84(altitude_m);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Altitude replace(double altitude_m) {
|
||||||
|
return new WGS84(altitude_m);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class EGM2008 extends Altitude {
|
public static class EGM2008 extends Altitude {
|
||||||
@@ -46,6 +53,11 @@ public abstract class Altitude {
|
|||||||
public static Altitude of(double altitude_m) {
|
public static Altitude of(double altitude_m) {
|
||||||
return new EGM2008(altitude_m);
|
return new EGM2008(altitude_m);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Altitude replace(double altitude_m) {
|
||||||
|
return new EGM2008(altitude_m);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -35,14 +35,15 @@ import de.dennisguse.opentracks.TrackActivityDataHubInterface;
|
|||||||
import de.dennisguse.opentracks.chart.ChartPoint;
|
import de.dennisguse.opentracks.chart.ChartPoint;
|
||||||
import de.dennisguse.opentracks.content.TrackDataHub;
|
import de.dennisguse.opentracks.content.TrackDataHub;
|
||||||
import de.dennisguse.opentracks.content.TrackDataListener;
|
import de.dennisguse.opentracks.content.TrackDataListener;
|
||||||
|
import de.dennisguse.opentracks.content.data.Altitude;
|
||||||
import de.dennisguse.opentracks.content.data.Marker;
|
import de.dennisguse.opentracks.content.data.Marker;
|
||||||
import de.dennisguse.opentracks.content.data.Speed;
|
import de.dennisguse.opentracks.content.data.Speed;
|
||||||
import de.dennisguse.opentracks.content.data.Track;
|
import de.dennisguse.opentracks.content.data.Track;
|
||||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||||
import de.dennisguse.opentracks.databinding.ChartBinding;
|
import de.dennisguse.opentracks.databinding.ChartBinding;
|
||||||
|
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
||||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||||
import de.dennisguse.opentracks.stats.TrackStatisticsUpdater;
|
import de.dennisguse.opentracks.stats.TrackStatisticsUpdater;
|
||||||
import de.dennisguse.opentracks.settings.PreferencesUtils;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A fragment to display track chart to the user.
|
* A fragment to display track chart to the user.
|
||||||
@@ -189,9 +190,9 @@ public class ChartFragment extends Fragment implements TrackDataListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onSampledInTrackPoint(@NonNull TrackPoint trackPoint, @NonNull TrackStatistics trackStatistics, Speed smoothedSpeed, double smoothedAltitude_m) {
|
public void onSampledInTrackPoint(@NonNull TrackPoint trackPoint, @NonNull TrackStatistics trackStatistics, Speed smoothedSpeed, Altitude smoothedAltitude) {
|
||||||
if (isResumed()) {
|
if (isResumed()) {
|
||||||
ChartPoint point = new ChartPoint(trackStatistics, trackPoint, smoothedSpeed, smoothedAltitude_m, chartByDistance, viewBinding.chartView.getMetricUnits());
|
ChartPoint point = new ChartPoint(trackStatistics, trackPoint, smoothedSpeed, smoothedAltitude, chartByDistance, viewBinding.chartView.getMetricUnits());
|
||||||
pendingPoints.add(point);
|
pendingPoints.add(point);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package de.dennisguse.opentracks.stats;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import de.dennisguse.opentracks.content.data.Altitude;
|
||||||
|
|
||||||
|
public class AltitudeRingBuffer extends RingBuffer<Altitude> {
|
||||||
|
|
||||||
|
private Altitude firstAltitude;
|
||||||
|
|
||||||
|
AltitudeRingBuffer(int size) {
|
||||||
|
super(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
AltitudeRingBuffer(RingBuffer<Altitude> toCopy) {
|
||||||
|
super(toCopy);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
protected Number from(Altitude object) {
|
||||||
|
if (firstAltitude == null) {
|
||||||
|
firstAltitude = object;
|
||||||
|
}
|
||||||
|
return object.toM();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Altitude to(double object) {
|
||||||
|
return firstAltitude.replace(object);
|
||||||
|
}
|
||||||
|
}
|
||||||
+38
-20
@@ -17,8 +17,9 @@
|
|||||||
package de.dennisguse.opentracks.stats;
|
package de.dennisguse.opentracks.stats;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.ArrayList;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class maintains a ring buffer of doubles.
|
* This class maintains a ring buffer of doubles.
|
||||||
@@ -27,10 +28,9 @@ import java.util.Arrays;
|
|||||||
*
|
*
|
||||||
* @author Sandor Dornbush
|
* @author Sandor Dornbush
|
||||||
*/
|
*/
|
||||||
class DoubleRingBuffer {
|
abstract class RingBuffer<T> {
|
||||||
|
|
||||||
// The sliding buffer of doubles.
|
private final ArrayList<T> buffer;
|
||||||
private final double[] buffer;
|
|
||||||
|
|
||||||
// The location that the next write will occur at.
|
// The location that the next write will occur at.
|
||||||
private int index;
|
private int index;
|
||||||
@@ -43,16 +43,17 @@ class DoubleRingBuffer {
|
|||||||
*
|
*
|
||||||
* @param size the size
|
* @param size the size
|
||||||
*/
|
*/
|
||||||
DoubleRingBuffer(int size) {
|
RingBuffer(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.");
|
||||||
}
|
}
|
||||||
buffer = new double[size];
|
|
||||||
|
buffer = new ArrayList<T>(size);
|
||||||
reset();
|
reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
DoubleRingBuffer(DoubleRingBuffer toCopy) {
|
RingBuffer(RingBuffer<T> toCopy) {
|
||||||
this.buffer = Arrays.copyOf(toCopy.buffer, toCopy.buffer.length);
|
this.buffer = new ArrayList<>(toCopy.buffer);
|
||||||
this.index = toCopy.index;
|
this.index = toCopy.index;
|
||||||
this.isFull = toCopy.isFull;
|
this.isFull = toCopy.isFull;
|
||||||
}
|
}
|
||||||
@@ -75,31 +76,48 @@ class DoubleRingBuffer {
|
|||||||
/**
|
/**
|
||||||
* Gets the average of the buffer.
|
* Gets the average of the buffer.
|
||||||
*/
|
*/
|
||||||
public double getAverage() {
|
public T getAverage() {
|
||||||
int numberOfEntries = isFull ? buffer.length : index;
|
int numberOfEntries = isFull ? buffer.size() : index;
|
||||||
if (numberOfEntries == 0) {
|
if (numberOfEntries == 0) {
|
||||||
return 0;
|
return null;
|
||||||
}
|
}
|
||||||
double sum = 0;
|
Double sum = null;
|
||||||
|
int numberOfUsedEntries = 0;
|
||||||
for (int i = 0; i < numberOfEntries; i++) {
|
for (int i = 0; i < numberOfEntries; i++) {
|
||||||
sum += buffer[i];
|
Number value = from(buffer.get(i));
|
||||||
|
if (value != null) {
|
||||||
|
if (sum == null) {
|
||||||
|
sum = 0.0;
|
||||||
|
}
|
||||||
|
sum += value.doubleValue();
|
||||||
|
numberOfUsedEntries++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (sum == null) {
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
return to(sum / numberOfUsedEntries);
|
||||||
}
|
}
|
||||||
return sum / numberOfEntries;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
protected abstract Number from(T object);
|
||||||
|
|
||||||
|
protected abstract T to(double object);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a double to the buffer.
|
* Adds a double to the buffer.
|
||||||
* If the buffer is full the oldest element is overwritten.
|
* If the buffer is full the oldest element is overwritten.
|
||||||
*
|
*
|
||||||
* @param value the double to add
|
* @param value the double to add
|
||||||
*/
|
*/
|
||||||
public void setNext(double value) {
|
public void setNext(T value) {
|
||||||
if (index == buffer.length) {
|
if (index == buffer.size()) {
|
||||||
index = 0;
|
index = 0;
|
||||||
}
|
}
|
||||||
buffer[index] = value;
|
buffer.add(index, value);
|
||||||
index++;
|
index++;
|
||||||
if (index == buffer.length) {
|
if (index == buffer.size()) {
|
||||||
isFull = true;
|
isFull = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -110,9 +128,9 @@ class DoubleRingBuffer {
|
|||||||
StringBuilder builder = new StringBuilder("Full: ");
|
StringBuilder builder = new StringBuilder("Full: ");
|
||||||
builder.append(isFull);
|
builder.append(isFull);
|
||||||
builder.append("\n");
|
builder.append("\n");
|
||||||
for (int i = 0; i < buffer.length; i++) {
|
for (int i = 0; i < buffer.size(); i++) {
|
||||||
builder.append((i == index) ? "<<" : "[");
|
builder.append((i == index) ? "<<" : "[");
|
||||||
builder.append(buffer[i]);
|
builder.append(buffer.get(i));
|
||||||
builder.append((i == index) ? ">> " : "] ");
|
builder.append((i == index) ? ">> " : "] ");
|
||||||
}
|
}
|
||||||
return builder.toString();
|
return builder.toString();
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package de.dennisguse.opentracks.stats;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import de.dennisguse.opentracks.content.data.Speed;
|
||||||
|
|
||||||
|
public class SpeedRingBuffer extends RingBuffer<Speed> {
|
||||||
|
|
||||||
|
SpeedRingBuffer(int size) {
|
||||||
|
super(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
SpeedRingBuffer(RingBuffer<Speed> toCopy) {
|
||||||
|
super(toCopy);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
protected Number from(Speed object) {
|
||||||
|
return object.toMPS();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Speed to(double object) {
|
||||||
|
return Speed.of(object);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -23,6 +23,7 @@ import androidx.annotation.VisibleForTesting;
|
|||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
|
|
||||||
|
import de.dennisguse.opentracks.content.data.Altitude;
|
||||||
import de.dennisguse.opentracks.content.data.Distance;
|
import de.dennisguse.opentracks.content.data.Distance;
|
||||||
import de.dennisguse.opentracks.content.data.Speed;
|
import de.dennisguse.opentracks.content.data.Speed;
|
||||||
|
|
||||||
@@ -258,8 +259,10 @@ public class TrackStatistics {
|
|||||||
altitudeExtremities.setMax(altitude_m);
|
altitudeExtremities.setMax(altitude_m);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateAltitudeExtremities(double altitude_m) {
|
public void updateAltitudeExtremities(Altitude altitude) {
|
||||||
altitudeExtremities.update(altitude_m);
|
if (altitude != null) {
|
||||||
|
altitudeExtremities.update(altitude.toM());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean hasTotalAltitudeGain() {
|
public boolean hasTotalAltitudeGain() {
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import androidx.annotation.VisibleForTesting;
|
|||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import de.dennisguse.opentracks.content.data.Altitude;
|
||||||
import de.dennisguse.opentracks.content.data.Distance;
|
import de.dennisguse.opentracks.content.data.Distance;
|
||||||
import de.dennisguse.opentracks.content.data.Speed;
|
import de.dennisguse.opentracks.content.data.Speed;
|
||||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||||
@@ -61,8 +62,8 @@ public class TrackStatisticsUpdater {
|
|||||||
|
|
||||||
private final TrackStatistics trackStatistics;
|
private final TrackStatistics trackStatistics;
|
||||||
|
|
||||||
private final DoubleRingBuffer altitudeBuffer_m;
|
private final AltitudeRingBuffer altitudeBuffer;
|
||||||
private final DoubleRingBuffer speedBuffer_mps;
|
private final SpeedRingBuffer speedBuffer;
|
||||||
|
|
||||||
// The current segment's statistics
|
// The current segment's statistics
|
||||||
private final TrackStatistics currentSegment;
|
private final TrackStatistics currentSegment;
|
||||||
@@ -82,16 +83,16 @@ public class TrackStatisticsUpdater {
|
|||||||
this.trackStatistics = trackStatistics;
|
this.trackStatistics = trackStatistics;
|
||||||
this.currentSegment = new TrackStatistics();
|
this.currentSegment = new TrackStatistics();
|
||||||
|
|
||||||
altitudeBuffer_m = new DoubleRingBuffer(ALTITUDE_SMOOTHING_FACTOR);
|
altitudeBuffer = new AltitudeRingBuffer(ALTITUDE_SMOOTHING_FACTOR);
|
||||||
speedBuffer_mps = new DoubleRingBuffer(SPEED_SMOOTHING_FACTOR);
|
speedBuffer = new SpeedRingBuffer(SPEED_SMOOTHING_FACTOR);
|
||||||
}
|
}
|
||||||
|
|
||||||
public TrackStatisticsUpdater(TrackStatisticsUpdater toCopy) {
|
public TrackStatisticsUpdater(TrackStatisticsUpdater toCopy) {
|
||||||
this.currentSegment = new TrackStatistics(toCopy.currentSegment);
|
this.currentSegment = new TrackStatistics(toCopy.currentSegment);
|
||||||
this.trackStatistics = new TrackStatistics(toCopy.trackStatistics);
|
this.trackStatistics = new TrackStatistics(toCopy.trackStatistics);
|
||||||
|
|
||||||
this.altitudeBuffer_m = new DoubleRingBuffer(toCopy.altitudeBuffer_m);
|
this.altitudeBuffer = new AltitudeRingBuffer(toCopy.altitudeBuffer);
|
||||||
this.speedBuffer_mps = new DoubleRingBuffer(toCopy.speedBuffer_mps);
|
this.speedBuffer = new SpeedRingBuffer(toCopy.speedBuffer);
|
||||||
|
|
||||||
this.lastTrackPoint = toCopy.lastTrackPoint;
|
this.lastTrackPoint = toCopy.lastTrackPoint;
|
||||||
}
|
}
|
||||||
@@ -136,7 +137,11 @@ public class TrackStatisticsUpdater {
|
|||||||
|
|
||||||
//Update absolute (GPS-based) altitude
|
//Update absolute (GPS-based) altitude
|
||||||
if (trackPoint.hasAltitude()) {
|
if (trackPoint.hasAltitude()) {
|
||||||
updateAbsoluteAltitude(trackPoint.getAltitude().toM());
|
// Update altitude using the smoothed average
|
||||||
|
altitudeBuffer.setNext(trackPoint.getAltitude());
|
||||||
|
Altitude newAverage = altitudeBuffer.getAverage();
|
||||||
|
|
||||||
|
currentSegment.updateAltitudeExtremities(newAverage);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update total distance
|
// Update total distance
|
||||||
@@ -164,7 +169,7 @@ public class TrackStatisticsUpdater {
|
|||||||
// Update max speed
|
// Update max speed
|
||||||
updateSpeed(trackPoint, lastTrackPoint);
|
updateSpeed(trackPoint, lastTrackPoint);
|
||||||
} else {
|
} else {
|
||||||
speedBuffer_mps.reset();
|
speedBuffer.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -183,20 +188,20 @@ public class TrackStatisticsUpdater {
|
|||||||
currentSegment.reset(trackPoint.getTime());
|
currentSegment.reset(trackPoint.getTime());
|
||||||
|
|
||||||
lastTrackPoint = null;
|
lastTrackPoint = null;
|
||||||
altitudeBuffer_m.reset();
|
altitudeBuffer.reset();
|
||||||
speedBuffer_mps.reset();
|
speedBuffer.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the smoothed altitude over several readings.
|
* Gets the smoothed altitude over several readings.
|
||||||
* The altitude readings is noisy so the smoothed altitude is better than the raw altitude for many tasks.
|
* The altitude readings is noisy so the smoothed altitude is better than the raw altitude for many tasks.
|
||||||
*/
|
*/
|
||||||
public double getSmoothedAltitude() {
|
public Altitude getSmoothedAltitude() {
|
||||||
return altitudeBuffer_m.getAverage();
|
return altitudeBuffer.getAverage();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Speed getSmoothedSpeed() {
|
public Speed getSmoothedSpeed() {
|
||||||
return Speed.of(speedBuffer_mps.getAverage());
|
return speedBuffer.getAverage();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -205,10 +210,10 @@ public class TrackStatisticsUpdater {
|
|||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
private void updateSpeed(@NonNull TrackPoint trackPoint, @NonNull TrackPoint lastTrackPoint) {
|
private void updateSpeed(@NonNull TrackPoint trackPoint, @NonNull TrackPoint lastTrackPoint) {
|
||||||
if (!trackPoint.isMoving()) {
|
if (!trackPoint.isMoving()) {
|
||||||
speedBuffer_mps.reset();
|
speedBuffer.reset();
|
||||||
} else if (isValidSpeed(trackPoint, lastTrackPoint)) {
|
} else if (isValidSpeed(trackPoint, lastTrackPoint)) {
|
||||||
speedBuffer_mps.setNext(trackPoint.getSpeed().toMPS());
|
speedBuffer.setNext(trackPoint.getSpeed());
|
||||||
Speed average = Speed.of(speedBuffer_mps.getAverage());
|
Speed average = speedBuffer.getAverage();
|
||||||
if (average.greaterThan(currentSegment.getMaxSpeed())) {
|
if (average.greaterThan(currentSegment.getMaxSpeed())) {
|
||||||
currentSegment.setMaxSpeed(average);
|
currentSegment.setMaxSpeed(average);
|
||||||
}
|
}
|
||||||
@@ -217,14 +222,6 @@ public class TrackStatisticsUpdater {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateAbsoluteAltitude(double altitude) {
|
|
||||||
// Update altitude using the smoothed average
|
|
||||||
altitudeBuffer_m.setNext(altitude);
|
|
||||||
double newAverage = altitudeBuffer_m.getAverage();
|
|
||||||
|
|
||||||
currentSegment.updateAltitudeExtremities(newAverage);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isValidSpeed(@NonNull TrackPoint trackPoint, @NonNull TrackPoint lastTrackPoint) {
|
private boolean isValidSpeed(@NonNull TrackPoint trackPoint, @NonNull TrackPoint lastTrackPoint) {
|
||||||
// There are a lot of noisy speed readings. Do the cheapest checks first, most expensive last.
|
// There are a lot of noisy speed readings. Do the cheapest checks first, most expensive last.
|
||||||
if (trackPoint.getSpeed().isZero()) {
|
if (trackPoint.getSpeed().isZero()) {
|
||||||
@@ -242,8 +239,8 @@ public class TrackStatisticsUpdater {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Only check if the speed buffer is full. Check that the speed is less than 10X the smoothed average and the speed difference doesn't imply 2g acceleration.
|
// Only check if the speed buffer is full. Check that the speed is less than 10X the smoothed average and the speed difference doesn't imply 2g acceleration.
|
||||||
if (speedBuffer_mps.isFull()) {
|
if (speedBuffer.isFull()) {
|
||||||
Speed average = Speed.of(speedBuffer_mps.getAverage());
|
Speed average = speedBuffer.getAverage();
|
||||||
Speed speedDifference = Speed.absDiff(average, trackPoint.getSpeed());
|
Speed speedDifference = Speed.absDiff(average, trackPoint.getSpeed());
|
||||||
|
|
||||||
return trackPoint.getSpeed().lessThan(average.mul(10)) && speedDifference.lessThan(maxAcceleration);
|
return trackPoint.getSpeed().lessThan(average.mul(10)) && speedDifference.lessThan(maxAcceleration);
|
||||||
|
|||||||
Reference in New Issue
Block a user