forked from upstream-mirrors/OpenTracks
@@ -48,6 +48,7 @@ import androidx.loader.content.Loader;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.Distance;
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
import de.dennisguse.opentracks.content.data.TracksColumns;
|
||||
import de.dennisguse.opentracks.databinding.TrackListBinding;
|
||||
@@ -215,7 +216,7 @@ public class TrackListActivity extends AbstractListActivity implements ConfirmDe
|
||||
int iconId = TrackIconUtils.getIconDrawable(icon);
|
||||
String name = cursor.getString(nameIndex);
|
||||
String totalTime = StringUtils.formatElapsedTime(Duration.ofMillis(cursor.getLong(totalTimeIndex)));
|
||||
String totalDistance = StringUtils.formatDistance(TrackListActivity.this, cursor.getDouble(totalDistanceIndex), metricUnits);
|
||||
String totalDistance = StringUtils.formatDistance(TrackListActivity.this, Distance.of(cursor.getDouble(totalDistanceIndex)), metricUnits);
|
||||
int markerCount = cursor.getInt(markerCountIndex);
|
||||
long startTime = cursor.getLong(startTimeIndex);
|
||||
String category = icon != null && !icon.equals("") ? null : cursor.getString(categoryIndex);
|
||||
|
||||
@@ -12,6 +12,8 @@ import androidx.recyclerview.widget.RecyclerView;
|
||||
import java.util.List;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.content.data.Distance;
|
||||
import de.dennisguse.opentracks.content.data.Speed;
|
||||
import de.dennisguse.opentracks.util.StringUtils;
|
||||
import de.dennisguse.opentracks.viewmodels.IntervalStatistics;
|
||||
|
||||
@@ -52,12 +54,12 @@ public class IntervalStatisticsAdapter extends RecyclerView.Adapter<RecyclerView
|
||||
} else {
|
||||
sumDistance_m = nextPosition * interval.getDistance_m();
|
||||
}
|
||||
viewHolder.distance.setText(StringUtils.formatDistance(context, sumDistance_m, metricUnits));
|
||||
viewHolder.distance.setText(StringUtils.formatDistance(context, Distance.of(sumDistance_m), metricUnits));
|
||||
|
||||
viewHolder.rate.setText(StringUtils.formatSpeed(context, interval.getSpeed_ms(), metricUnits, isReportSpeed));
|
||||
viewHolder.rate.setText(StringUtils.formatSpeed(context, Speed.of(interval.getSpeed_ms()), metricUnits, isReportSpeed));
|
||||
|
||||
viewHolder.gain.setText(StringUtils.formatDistance(context, interval.getGain_m(), metricUnits));
|
||||
viewHolder.loss.setText(StringUtils.formatDistance(context, interval.getLoss_m(), metricUnits));
|
||||
viewHolder.gain.setText(StringUtils.formatDistance(context, Distance.of(interval.getGain_m()), metricUnits));
|
||||
viewHolder.loss.setText(StringUtils.formatDistance(context, Distance.of(interval.getLoss_m()), metricUnits));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -29,11 +29,7 @@ public class ChartPoint {
|
||||
TrackStatistics trackStatistics = trackStatisticsUpdater.getTrackStatistics();
|
||||
|
||||
if (chartByDistance) {
|
||||
double distance = trackStatistics.getTotalDistance() * UnitConversions.M_TO_KM;
|
||||
if (!metricUnits) {
|
||||
distance *= UnitConversions.KM_TO_MI;
|
||||
}
|
||||
timeOrDistance = distance;
|
||||
timeOrDistance = trackStatistics.getTotalDistance().to(metricUnits);
|
||||
} else {
|
||||
timeOrDistance = trackStatistics.getTotalTime().toMillis();
|
||||
}
|
||||
@@ -43,11 +39,8 @@ public class ChartPoint {
|
||||
altitude *= UnitConversions.M_TO_FT;
|
||||
}
|
||||
|
||||
speed = trackStatisticsUpdater.getSmoothedSpeed() * UnitConversions.MPS_TO_KMH;
|
||||
if (!metricUnits) {
|
||||
speed *= UnitConversions.KM_TO_MI;
|
||||
}
|
||||
pace = speed == 0 ? 0.0 : 60.0 / speed;
|
||||
speed = trackStatisticsUpdater.getSmoothedSpeed().to(metricUnits);
|
||||
pace = trackStatisticsUpdater.getSmoothedSpeed().toPace(metricUnits).toMillis() * UnitConversions.MS_TO_S * UnitConversions.S_TO_MIN;
|
||||
if (trackPoint != null) {
|
||||
if (trackPoint.hasHeartRate()) {
|
||||
heartRate = trackPoint.getHeartRate_bpm();
|
||||
|
||||
@@ -51,7 +51,6 @@ import de.dennisguse.opentracks.util.IntentUtils;
|
||||
import de.dennisguse.opentracks.util.MarkerUtils;
|
||||
import de.dennisguse.opentracks.util.StringUtils;
|
||||
import de.dennisguse.opentracks.util.ThemeUtils;
|
||||
import de.dennisguse.opentracks.util.UnitConversions;
|
||||
|
||||
/**
|
||||
* Visualization of the chart.
|
||||
@@ -994,8 +993,7 @@ public class ChartView extends View {
|
||||
|
||||
private double getMarkerXValue(Marker marker) {
|
||||
if (chartByDistance) {
|
||||
double length_km = marker.getLength() * UnitConversions.M_TO_KM;
|
||||
return metricUnits ? length_km : length_km * UnitConversions.KM_TO_MI;
|
||||
return marker.getLength().to(metricUnits);
|
||||
} else {
|
||||
return marker.getDuration().toMillis();
|
||||
}
|
||||
|
||||
@@ -24,6 +24,8 @@ import androidx.annotation.VisibleForTesting;
|
||||
import java.time.Duration;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.content.data.Distance;
|
||||
import de.dennisguse.opentracks.content.data.Speed;
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
import de.dennisguse.opentracks.util.StringUtils;
|
||||
@@ -163,9 +165,9 @@ public class DescriptionGenerator {
|
||||
* @param lineBreak line break string
|
||||
*/
|
||||
@VisibleForTesting
|
||||
void writeDistance(double distance, StringBuilder builder, int resId, String lineBreak) {
|
||||
double distanceInKm = distance * UnitConversions.M_TO_KM;
|
||||
double distanceInMi = distanceInKm * UnitConversions.KM_TO_MI;
|
||||
void writeDistance(Distance distance, StringBuilder builder, int resId, String lineBreak) {
|
||||
double distanceInKm = distance.toKM();
|
||||
double distanceInMi = distance.toMI();
|
||||
builder.append(context.getString(resId, distanceInKm, distanceInMi));
|
||||
builder.append(lineBreak);
|
||||
}
|
||||
@@ -193,21 +195,18 @@ public class DescriptionGenerator {
|
||||
* @param lineBreak line break string
|
||||
*/
|
||||
@VisibleForTesting
|
||||
void writeSpeed(double speed, StringBuilder builder, int resId, String lineBreak) {
|
||||
double speedInKmHr = speed * UnitConversions.MPS_TO_KMH;
|
||||
double speedInMiHr = speedInKmHr * UnitConversions.KM_TO_MI;
|
||||
builder.append(context.getString(resId, speedInKmHr, speedInMiHr));
|
||||
void writeSpeed(Speed speed, StringBuilder builder, int resId, String lineBreak) {
|
||||
builder.append(context.getString(resId, speed.toKMH(), speed.toMPH()));
|
||||
builder.append(lineBreak);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param speed speed in meters per second
|
||||
* @param builder StringBuilder to append pace
|
||||
* @param resId resource id of pace string
|
||||
* @param lineBreak line break string
|
||||
*/
|
||||
@VisibleForTesting
|
||||
void writePace(double speed, StringBuilder builder, int resId, String lineBreak) {
|
||||
void writePace(Speed speed, StringBuilder builder, int resId, String lineBreak) {
|
||||
Pair<String, String> paceInMetrics = StringUtils.getSpeedParts(context, speed, true, false);
|
||||
Pair<String, String> paceInImperial = StringUtils.getSpeedParts(context, speed, false, false);
|
||||
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
package de.dennisguse.opentracks.content.data;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import de.dennisguse.opentracks.util.UnitConversions;
|
||||
|
||||
public class Distance {
|
||||
|
||||
public static Distance of(double distance_m) {
|
||||
return new Distance(distance_m);
|
||||
}
|
||||
|
||||
public static Distance of(String distance_m) {
|
||||
return of(Float.parseFloat(distance_m));
|
||||
}
|
||||
|
||||
public static Distance ofMile(double distance_mile) {
|
||||
return of(distance_mile * UnitConversions.MI_TO_M);
|
||||
}
|
||||
|
||||
public static Distance invalid() {
|
||||
return of(Double.NaN);
|
||||
}
|
||||
|
||||
private final double distance_m;
|
||||
|
||||
private Distance(double distance_m) {
|
||||
this.distance_m = distance_m;
|
||||
}
|
||||
|
||||
public Distance plus(@NonNull Distance distance) {
|
||||
return new Distance(distance_m + distance.distance_m);
|
||||
}
|
||||
|
||||
public Distance multipliedBy(double factor) {
|
||||
return new Distance(factor * distance_m);
|
||||
}
|
||||
|
||||
public boolean isZero() {
|
||||
return distance_m == 0;
|
||||
}
|
||||
|
||||
public boolean isInvalid() {
|
||||
return Double.isNaN(distance_m) || Double.isInfinite(distance_m);
|
||||
}
|
||||
|
||||
public boolean lessThan(@NonNull Distance distance) {
|
||||
return !greaterThan(distance);
|
||||
}
|
||||
|
||||
public boolean greaterThan(@NonNull Distance distance) {
|
||||
return distance_m > distance.distance_m;
|
||||
}
|
||||
|
||||
public boolean greaterOrEqualThan(@NonNull Distance distance) {
|
||||
return distance_m >= distance.distance_m;
|
||||
}
|
||||
|
||||
public double toM() {
|
||||
return distance_m;
|
||||
}
|
||||
|
||||
public double toKM() {
|
||||
return distance_m * UnitConversions.M_TO_KM;
|
||||
}
|
||||
|
||||
public double toFT() {
|
||||
return distance_m * UnitConversions.M_TO_FT;
|
||||
}
|
||||
|
||||
public double toMI() {
|
||||
return toKM() * UnitConversions.KM_TO_MI;
|
||||
}
|
||||
|
||||
public double to(boolean metricUnit) {
|
||||
return to(metricUnit ? Unit.KM : Unit.MILES);
|
||||
}
|
||||
|
||||
public double to(Unit unit) {
|
||||
switch (unit) {
|
||||
case KM:
|
||||
return toKM();
|
||||
case MILES:
|
||||
return toMI();
|
||||
default:
|
||||
throw new RuntimeException("Not implemented");
|
||||
}
|
||||
}
|
||||
|
||||
public enum Unit {
|
||||
KM,
|
||||
MILES
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
Distance distance = (Distance) o;
|
||||
return Double.compare(distance.distance_m, distance_m) == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(distance_m);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Distance{" +
|
||||
"distance_m=" + distance_m +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@@ -28,12 +28,15 @@ import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.Objects;
|
||||
|
||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
|
||||
/**
|
||||
* NOTE: A marker is indirectly (via it's location) assigned to one {@link TrackPoint} with trackPoint.hasLocation() == true.
|
||||
*
|
||||
* @author Leif Hendrik Wilden
|
||||
* @author Rodrigo Damazio
|
||||
*/
|
||||
//TODO All data should be final; no default values.
|
||||
public final class Marker {
|
||||
|
||||
private Id id;
|
||||
@@ -51,7 +54,7 @@ public final class Marker {
|
||||
private Float bearing;
|
||||
|
||||
//TODO It is the distance from the track starting point; rename to something more meaningful
|
||||
private double length = 0.0;
|
||||
private Distance length;
|
||||
private Duration duration;
|
||||
|
||||
@Deprecated //TODO Make an URI instead of String
|
||||
@@ -62,6 +65,7 @@ public final class Marker {
|
||||
this.time = time;
|
||||
}
|
||||
|
||||
@Deprecated //TODO Marker cannot be created without length AND duration!
|
||||
public Marker(@Nullable Track.Id trackId, @NonNull TrackPoint trackPoint) {
|
||||
this.trackId = trackId;
|
||||
|
||||
@@ -75,16 +79,19 @@ public final class Marker {
|
||||
if (trackPoint.hasAccuracy()) this.accuracy = trackPoint.getAccuracy();
|
||||
if (trackPoint.hasAltitude()) this.altitude_m = trackPoint.getAltitude();
|
||||
if (trackPoint.hasBearing()) this.bearing = trackPoint.getBearing();
|
||||
|
||||
this.length = Distance.of(0); //TODO Not cool!
|
||||
this.duration = Duration.ofMillis(0); //TODO Not cool!
|
||||
}
|
||||
|
||||
public Marker(String name, String description, String category, String icon, @NonNull Track.Id trackId, double length, @NonNull Duration duration, @NonNull TrackPoint trackPoint, String photoUrl) {
|
||||
public Marker(String name, String description, String category, String icon, @NonNull Track.Id trackId, @NonNull TrackStatistics statistics, @NonNull TrackPoint trackPoint, String photoUrl) {
|
||||
this(trackId, trackPoint);
|
||||
this.name = name;
|
||||
this.description = description;
|
||||
this.category = category;
|
||||
this.icon = icon;
|
||||
this.length = length;
|
||||
this.duration = duration;
|
||||
this.length = statistics.getTotalDistance();
|
||||
this.duration = statistics.getTotalTime();
|
||||
this.photoUrl = photoUrl;
|
||||
}
|
||||
|
||||
@@ -218,11 +225,11 @@ public final class Marker {
|
||||
this.bearing = bearing;
|
||||
}
|
||||
|
||||
public double getLength() {
|
||||
public Distance getLength() {
|
||||
return length;
|
||||
}
|
||||
|
||||
public void setLength(double length) {
|
||||
public void setLength(Distance length) {
|
||||
this.length = length;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
package de.dennisguse.opentracks.content.data;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Objects;
|
||||
|
||||
import de.dennisguse.opentracks.util.UnitConversions;
|
||||
|
||||
public class Speed {
|
||||
|
||||
public static Speed of(Distance distance, Duration duration) {
|
||||
if (duration.isZero()) {
|
||||
return zero();
|
||||
}
|
||||
|
||||
return new Speed(distance.toM() / (duration.toMillis() * UnitConversions.MS_TO_S));
|
||||
}
|
||||
|
||||
public static Speed of(double speed_mps) {
|
||||
return new Speed(speed_mps);
|
||||
}
|
||||
|
||||
public static Speed of(String speed_mps) {
|
||||
return of(Float.parseFloat(speed_mps));
|
||||
}
|
||||
|
||||
public static Speed invalid() {
|
||||
return of(Double.NaN);
|
||||
}
|
||||
|
||||
public static Speed zero() {
|
||||
return of(0.0);
|
||||
}
|
||||
|
||||
public static Speed max(Speed speed1, Speed speed2) {
|
||||
if (speed1.greaterThan(speed2)) {
|
||||
return speed1;
|
||||
}
|
||||
|
||||
return speed2;
|
||||
}
|
||||
|
||||
public static Speed absDiff(Speed speed1, Speed speed2) {
|
||||
//TODO Why Math.abs? Seems to be a leftover.
|
||||
return Speed.of(Math.abs(speed1.speed_mps - speed2.speed_mps));
|
||||
}
|
||||
|
||||
|
||||
// Anything faster than that (in meters per second) will be considered moving.
|
||||
private static final double MAX_NO_MOVEMENT_SPEED = 0.224;
|
||||
|
||||
private final double speed_mps;
|
||||
|
||||
private Speed(double speed_mps) {
|
||||
this.speed_mps = speed_mps;
|
||||
}
|
||||
|
||||
public Speed mul(double factor) {
|
||||
return new Speed(factor * speed_mps);
|
||||
}
|
||||
|
||||
public boolean isZero() {
|
||||
return speed_mps == 0;
|
||||
}
|
||||
|
||||
public boolean isInvalid() {
|
||||
return Double.isNaN(speed_mps) || Double.isInfinite(speed_mps);
|
||||
}
|
||||
|
||||
public boolean isMoving() {
|
||||
return !isInvalid() && speed_mps >= MAX_NO_MOVEMENT_SPEED;
|
||||
}
|
||||
|
||||
public boolean lessThan(Speed speed) {
|
||||
return !greaterThan(speed);
|
||||
}
|
||||
|
||||
public boolean greaterThan(Speed speed) {
|
||||
return speed_mps > speed.speed_mps;
|
||||
}
|
||||
|
||||
public boolean greaterOrEqualThan(Speed speed) {
|
||||
return speed_mps >= speed.speed_mps;
|
||||
}
|
||||
|
||||
public double toMPS() {
|
||||
return speed_mps;
|
||||
}
|
||||
|
||||
public double toKMH() {
|
||||
return speed_mps * UnitConversions.MPS_TO_KMH;
|
||||
}
|
||||
|
||||
public double toMPH() {
|
||||
return toKMH() * UnitConversions.KM_TO_MI;
|
||||
}
|
||||
|
||||
public Duration toPace(boolean metricUnit) {
|
||||
if (isZero()) {
|
||||
return Duration.ofSeconds(0);
|
||||
}
|
||||
|
||||
double distance = speed_mps * (metricUnit ? UnitConversions.M_TO_KM : UnitConversions.M_TO_MI);
|
||||
return Duration.ofSeconds(Math.round(1 / distance));
|
||||
}
|
||||
|
||||
public double to(boolean metricUnit) {
|
||||
return to(metricUnit ? Unit.KMH : Unit.MPH);
|
||||
}
|
||||
|
||||
public double to(Unit unit) {
|
||||
switch (unit) {
|
||||
case KMH:
|
||||
return toKMH();
|
||||
case MPH:
|
||||
return toMPH();
|
||||
default:
|
||||
throw new RuntimeException("Not implemented");
|
||||
}
|
||||
}
|
||||
|
||||
public enum Unit {
|
||||
KMH,
|
||||
MPH,
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
Speed speed = (Speed) o;
|
||||
return Double.compare(speed.speed_mps, speed_mps) == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(speed_mps);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Speed{" +
|
||||
"speed_mps=" + speed_mps +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@@ -45,9 +45,6 @@ public class TrackPoint {
|
||||
|
||||
private static final Duration MAX_LOCATION_AGE = Duration.ofMinutes(1);
|
||||
|
||||
// Anything faster than that (in meters per second) will be considered moving.
|
||||
private static final double MAX_NO_MOVEMENT_SPEED = 0.224;
|
||||
|
||||
private TrackPoint.Id id;
|
||||
|
||||
private Instant time;
|
||||
@@ -55,9 +52,9 @@ public class TrackPoint {
|
||||
private Double longitude;
|
||||
private Float accuracy;
|
||||
private Double altitude_m;
|
||||
private Float speed_mps;
|
||||
private Speed speed;
|
||||
private Float bearing;
|
||||
private Float sensorDistance_m;
|
||||
private Distance sensorDistance_m;
|
||||
|
||||
public enum Type {
|
||||
SEGMENT_START_MANUAL(-2), //Start of a segment due to user interaction (start, resume)
|
||||
@@ -106,7 +103,7 @@ public class TrackPoint {
|
||||
this.latitude = location.getLatitude();
|
||||
this.longitude = location.getLongitude();
|
||||
this.altitude_m = location.getAltitude();
|
||||
this.speed_mps = location.getSpeed();
|
||||
this.speed = Speed.of(location.getSpeed());
|
||||
this.accuracy = location.getAccuracy();
|
||||
|
||||
setTime(Instant.now());
|
||||
@@ -272,19 +269,19 @@ public class TrackPoint {
|
||||
}
|
||||
|
||||
public boolean hasSpeed() {
|
||||
return speed_mps != null;
|
||||
return speed != null;
|
||||
}
|
||||
|
||||
public float getSpeed() {
|
||||
return speed_mps;
|
||||
public Speed getSpeed() {
|
||||
return speed;
|
||||
}
|
||||
|
||||
public void setSpeed(Float speed) {
|
||||
this.speed_mps = speed;
|
||||
public void setSpeed(Speed speed) {
|
||||
this.speed = speed;
|
||||
}
|
||||
|
||||
public boolean isMoving() {
|
||||
return hasSpeed() && getSpeed() >= MAX_NO_MOVEMENT_SPEED;
|
||||
return hasSpeed() && getSpeed().isMoving();
|
||||
}
|
||||
|
||||
public boolean hasBearing() {
|
||||
@@ -311,12 +308,12 @@ public class TrackPoint {
|
||||
this.accuracy = horizontalAccuracy;
|
||||
}
|
||||
|
||||
public float distanceToPrevious(@NonNull TrackPoint previous) {
|
||||
public Distance distanceToPrevious(@NonNull TrackPoint previous) {
|
||||
if (hasSensorDistance()) {
|
||||
return getSensorDistance();
|
||||
}
|
||||
|
||||
return getLocation().distanceTo(previous.getLocation());
|
||||
return Distance.of(getLocation().distanceTo(previous.getLocation()));
|
||||
}
|
||||
|
||||
public boolean fulfillsAccuracy(int poorAccuracy) {
|
||||
@@ -336,11 +333,11 @@ public class TrackPoint {
|
||||
return sensorDistance_m != null;
|
||||
}
|
||||
|
||||
public Float getSensorDistance() {
|
||||
public Distance getSensorDistance() {
|
||||
return sensorDistance_m;
|
||||
}
|
||||
|
||||
public void setSensorDistance(Float distance_m) {
|
||||
public void setSensorDistance(Distance distance_m) {
|
||||
this.sensorDistance_m = distance_m;
|
||||
}
|
||||
|
||||
|
||||
@@ -39,8 +39,10 @@ import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import de.dennisguse.opentracks.BuildConfig;
|
||||
import de.dennisguse.opentracks.content.data.Distance;
|
||||
import de.dennisguse.opentracks.content.data.Marker;
|
||||
import de.dennisguse.opentracks.content.data.MarkerColumns;
|
||||
import de.dennisguse.opentracks.content.data.Speed;
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
import de.dennisguse.opentracks.content.data.TrackPointsColumns;
|
||||
@@ -127,7 +129,7 @@ public class ContentProviderUtils {
|
||||
trackStatistics.setStopTime(Instant.ofEpochMilli(cursor.getLong(stopTimeIndex)));
|
||||
}
|
||||
if (!cursor.isNull(totalDistanceIndex)) {
|
||||
trackStatistics.setTotalDistance(cursor.getFloat(totalDistanceIndex));
|
||||
trackStatistics.setTotalDistance(Distance.of(cursor.getFloat(totalDistanceIndex)));
|
||||
}
|
||||
if (!cursor.isNull(totalTimeIndex)) {
|
||||
trackStatistics.setTotalTime(Duration.ofMillis(cursor.getLong(totalTimeIndex)));
|
||||
@@ -136,7 +138,7 @@ public class ContentProviderUtils {
|
||||
trackStatistics.setMovingTime(Duration.ofMillis(cursor.getLong(movingTimeIndex)));
|
||||
}
|
||||
if (!cursor.isNull(maxSpeedIndex)) {
|
||||
trackStatistics.setMaxSpeed(cursor.getFloat(maxSpeedIndex));
|
||||
trackStatistics.setMaxSpeed(Speed.of(cursor.getFloat(maxSpeedIndex)));
|
||||
}
|
||||
if (!cursor.isNull(minAltitudeIndex)) {
|
||||
trackStatistics.setMinAltitude(cursor.getFloat(minAltitudeIndex));
|
||||
@@ -278,12 +280,12 @@ public class ContentProviderUtils {
|
||||
if (trackStatistics.getStopTime() != null) {
|
||||
values.put(TracksColumns.STOPTIME, trackStatistics.getStopTime().toEpochMilli());
|
||||
}
|
||||
values.put(TracksColumns.TOTALDISTANCE, trackStatistics.getTotalDistance());
|
||||
values.put(TracksColumns.TOTALDISTANCE, trackStatistics.getTotalDistance().toM());
|
||||
values.put(TracksColumns.TOTALTIME, trackStatistics.getTotalTime().toMillis());
|
||||
values.put(TracksColumns.MOVINGTIME, trackStatistics.getMovingTime().toMillis());
|
||||
values.put(TracksColumns.AVGSPEED, trackStatistics.getAverageSpeed());
|
||||
values.put(TracksColumns.AVGMOVINGSPEED, trackStatistics.getAverageMovingSpeed());
|
||||
values.put(TracksColumns.MAXSPEED, trackStatistics.getMaxSpeed());
|
||||
values.put(TracksColumns.AVGSPEED, trackStatistics.getAverageSpeed().toMPS());
|
||||
values.put(TracksColumns.AVGMOVINGSPEED, trackStatistics.getAverageMovingSpeed().toMPS());
|
||||
values.put(TracksColumns.MAXSPEED, trackStatistics.getMaxSpeed().toMPS());
|
||||
values.put(TracksColumns.MIN_ALTITUDE, trackStatistics.getMinAltitude());
|
||||
values.put(TracksColumns.MAX_ALTITUDE, trackStatistics.getMaxAltitude());
|
||||
values.put(TracksColumns.ALTITUDE_GAIN, trackStatistics.getTotalAltitudeGain());
|
||||
@@ -343,7 +345,7 @@ public class ContentProviderUtils {
|
||||
marker.setIcon(cursor.getString(iconIndex));
|
||||
}
|
||||
if (!cursor.isNull(lengthIndex)) {
|
||||
marker.setLength(cursor.getFloat(lengthIndex));
|
||||
marker.setLength(Distance.of(cursor.getFloat(lengthIndex)));
|
||||
}
|
||||
if (!cursor.isNull(durationIndex)) {
|
||||
marker.setDuration(Duration.ofMillis(cursor.getLong(durationIndex)));
|
||||
@@ -480,7 +482,7 @@ public class ContentProviderUtils {
|
||||
values.put(MarkerColumns.CATEGORY, marker.getCategory());
|
||||
values.put(MarkerColumns.ICON, marker.getIcon());
|
||||
values.put(MarkerColumns.TRACKID, marker.getTrackId().getId());
|
||||
values.put(MarkerColumns.LENGTH, marker.getLength());
|
||||
values.put(MarkerColumns.LENGTH, marker.getLength().toM());
|
||||
values.put(MarkerColumns.DURATION, marker.getDuration().toMillis());
|
||||
|
||||
values.put(MarkerColumns.LONGITUDE, (int) (marker.getLongitude() * 1E6));
|
||||
@@ -543,7 +545,7 @@ public class ContentProviderUtils {
|
||||
trackPoint.setAccuracy(cursor.getFloat(indexes.accuracyIndex));
|
||||
}
|
||||
if (!cursor.isNull(indexes.speedIndex)) {
|
||||
trackPoint.setSpeed(cursor.getFloat(indexes.speedIndex));
|
||||
trackPoint.setSpeed(Speed.of(cursor.getFloat(indexes.speedIndex)));
|
||||
}
|
||||
if (!cursor.isNull(indexes.bearingIndex)) {
|
||||
trackPoint.setBearing(cursor.getFloat(indexes.bearingIndex));
|
||||
@@ -556,7 +558,7 @@ public class ContentProviderUtils {
|
||||
trackPoint.setCyclingCadence_rpm(cursor.getFloat(indexes.sensorCadenceIndex));
|
||||
}
|
||||
if (!cursor.isNull(indexes.sensorDistanceIndex)) {
|
||||
trackPoint.setSensorDistance(cursor.getFloat(indexes.sensorDistanceIndex));
|
||||
trackPoint.setSensorDistance(Distance.of(cursor.getFloat(indexes.sensorDistanceIndex)));
|
||||
}
|
||||
if (!cursor.isNull(indexes.sensorPowerIndex)) {
|
||||
trackPoint.setPower(cursor.getFloat(indexes.sensorPowerIndex));
|
||||
@@ -694,7 +696,7 @@ public class ContentProviderUtils {
|
||||
values.put(TrackPointsColumns.ACCURACY, trackPoint.getAccuracy());
|
||||
}
|
||||
if (trackPoint.hasSpeed()) {
|
||||
values.put(TrackPointsColumns.SPEED, trackPoint.getSpeed());
|
||||
values.put(TrackPointsColumns.SPEED, trackPoint.getSpeed().toMPS());
|
||||
}
|
||||
if (trackPoint.hasBearing()) {
|
||||
values.put(TrackPointsColumns.BEARING, trackPoint.getBearing());
|
||||
@@ -707,7 +709,7 @@ public class ContentProviderUtils {
|
||||
values.put(TrackPointsColumns.SENSOR_CADENCE, trackPoint.getCyclingCadence_rpm());
|
||||
}
|
||||
if (trackPoint.hasSensorDistance()) {
|
||||
values.put(TrackPointsColumns.SENSOR_DISTANCE, trackPoint.getSensorDistance());
|
||||
values.put(TrackPointsColumns.SENSOR_DISTANCE, trackPoint.getSensorDistance().toM());
|
||||
}
|
||||
if (trackPoint.hasPower()) {
|
||||
values.put(TrackPointsColumns.SENSOR_POWER, trackPoint.getPower());
|
||||
|
||||
@@ -6,6 +6,10 @@ import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.util.Pair;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.Distance;
|
||||
import de.dennisguse.opentracks.content.data.Speed;
|
||||
import de.dennisguse.opentracks.util.UintUtils;
|
||||
import de.dennisguse.opentracks.util.UnitConversions;
|
||||
|
||||
@@ -122,21 +126,21 @@ public final class SensorDataCycling {
|
||||
public void compute(DistanceSpeed previous, int wheel_circumference_mm) {
|
||||
if (hasData() && previous != null && previous.hasData()) {
|
||||
float timeDiff_ms = UintUtils.diff(wheelRevolutionsTime, previous.wheelRevolutionsTime, UintUtils.UINT16_MAX) / 1024f * UnitConversions.S_TO_MS;
|
||||
if (timeDiff_ms <= 0) {
|
||||
Duration timeDiff = Duration.ofMillis((long) timeDiff_ms);
|
||||
if (timeDiff.isZero() || timeDiff.isNegative()) {
|
||||
Log.e(TAG, "Timestamps difference is invalid: cannot compute cadence.");
|
||||
value = null;
|
||||
} else {
|
||||
long wheelDiff = UintUtils.diff(wheelRevolutionsCount, previous.wheelRevolutionsCount, UintUtils.UINT16_MAX);
|
||||
wheelDiff = Math.abs(wheelDiff); //HACK for Garmin Speed 2 as it some of those seem to count backwards
|
||||
wheelDiff = Math.abs(wheelDiff); //HACK for Garmin Speed 2 as some of those seem to count backwards
|
||||
|
||||
double timeDiff_s = timeDiff_ms * UnitConversions.MS_TO_S;
|
||||
float distance_m = (float) (wheelDiff * wheel_circumference_mm * UnitConversions.MM_TO_M);
|
||||
float distance_overall_m = distance_m;
|
||||
Distance distance = Distance.of(wheelDiff * wheel_circumference_mm * UnitConversions.MM_TO_M);
|
||||
Distance distanceOverall = distance;
|
||||
if (previous.hasValue()) {
|
||||
distance_overall_m += previous.getValue().distance_overall_m;
|
||||
distanceOverall = distance.plus(previous.getValue().distanceOverall);
|
||||
}
|
||||
float speed_mps = (float) (distance_m / timeDiff_s);
|
||||
value = new Data(distance_m, distance_overall_m, speed_mps);
|
||||
Speed speed_mps = Speed.of(distance, timeDiff);
|
||||
value = new Data(distance, distanceOverall, speed_mps);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -144,7 +148,7 @@ public final class SensorDataCycling {
|
||||
@Override
|
||||
public void reset() {
|
||||
if (value != null) {
|
||||
value = new Data(value.distance_m, 0, value.speed_mps);
|
||||
value = new Data(value.distance, Distance.of(0), value.speed);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,22 +171,22 @@ public final class SensorDataCycling {
|
||||
}
|
||||
|
||||
public static class Data {
|
||||
public final float distance_m;
|
||||
public final float distance_overall_m;
|
||||
public final float speed_mps;
|
||||
public final Distance distance;
|
||||
public final Distance distanceOverall;
|
||||
public final Speed speed;
|
||||
|
||||
private Data(float distance_m, float distance_overall_m, float speed_mps) {
|
||||
this.distance_m = distance_m;
|
||||
this.distance_overall_m = distance_overall_m;
|
||||
this.speed_mps = speed_mps;
|
||||
private Data(Distance distance, Distance distanceOverall, Speed speed) {
|
||||
this.distance = distance;
|
||||
this.distanceOverall = distanceOverall;
|
||||
this.speed = speed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Data{" +
|
||||
"distance_m=" + distance_m +
|
||||
", distance_overall_m=" + distance_overall_m +
|
||||
", speed_mps=" + speed_mps +
|
||||
"distance_m=" + distance +
|
||||
", distance_overall_m=" + distanceOverall +
|
||||
", speed_mps=" + speed +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,8 +58,8 @@ public final class SensorDataSet {
|
||||
}
|
||||
|
||||
if (cyclingDistanceSpeed != null && cyclingDistanceSpeed.hasValue()) {
|
||||
trackPoint.setSensorDistance(cyclingDistanceSpeed.getValue().distance_overall_m);
|
||||
trackPoint.setSpeed(cyclingDistanceSpeed.getValue().speed_mps);
|
||||
trackPoint.setSensorDistance(cyclingDistanceSpeed.getValue().distanceOverall);
|
||||
trackPoint.setSpeed(cyclingDistanceSpeed.getValue().speed);
|
||||
}
|
||||
|
||||
if (cyclingPower != null && cyclingPower.hasValue()) {
|
||||
|
||||
@@ -37,6 +37,7 @@ import de.dennisguse.opentracks.chart.ChartPoint;
|
||||
import de.dennisguse.opentracks.chart.ChartView;
|
||||
import de.dennisguse.opentracks.content.TrackDataHub;
|
||||
import de.dennisguse.opentracks.content.TrackDataListener;
|
||||
import de.dennisguse.opentracks.content.data.Distance;
|
||||
import de.dennisguse.opentracks.content.data.Marker;
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
@@ -66,7 +67,7 @@ public class ChartFragment extends Fragment implements TrackDataListener {
|
||||
}
|
||||
|
||||
private SharedPreferences sharedPreferences;
|
||||
private int recordingDistanceInterval;
|
||||
private Distance recordingDistanceInterval;
|
||||
|
||||
private TrackDataHub trackDataHub;
|
||||
|
||||
@@ -346,4 +347,9 @@ public class ChartFragment extends Fragment implements TrackDataListener {
|
||||
void setChartByDistance(boolean value) {
|
||||
chartByDistance = value;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void setRecordingDistanceInterval(Distance value) {
|
||||
recordingDistanceInterval = value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -180,8 +180,7 @@ public class StatisticsRecordedFragment extends Fragment {
|
||||
private void updateUI() {
|
||||
// Set total distance
|
||||
{
|
||||
double totalDistance = trackStatistics.getTotalDistance();
|
||||
Pair<String, String> parts = StringUtils.getDistanceParts(getContext(), totalDistance, preferenceMetricUnits);
|
||||
Pair<String, String> parts = StringUtils.getDistanceParts(getContext(), trackStatistics.getTotalDistance(), preferenceMetricUnits);
|
||||
|
||||
viewBinding.statsDistanceValue.setText(parts.first);
|
||||
viewBinding.statsDistanceUnit.setText(parts.second);
|
||||
@@ -201,32 +200,27 @@ public class StatisticsRecordedFragment extends Fragment {
|
||||
|
||||
// Set average speed/pace
|
||||
{
|
||||
double speed = trackStatistics.getAverageSpeed();
|
||||
viewBinding.statsAverageSpeedLabel.setText(preferenceReportSpeed ? R.string.stats_average_speed : R.string.stats_average_pace);
|
||||
|
||||
Pair<String, String> parts = StringUtils.getSpeedParts(getContext(), speed, preferenceMetricUnits, preferenceReportSpeed);
|
||||
Pair<String, String> parts = StringUtils.getSpeedParts(getContext(), trackStatistics.getAverageSpeed(), preferenceMetricUnits, preferenceReportSpeed);
|
||||
viewBinding.statsAverageSpeedValue.setText(parts.first);
|
||||
viewBinding.statsAverageSpeedUnit.setText(parts.second);
|
||||
}
|
||||
|
||||
// Set max speed/pace
|
||||
{
|
||||
double speed = trackStatistics.getMaxSpeed();
|
||||
|
||||
viewBinding.statsMaxSpeedLabel.setText(preferenceReportSpeed ? R.string.stats_max_speed : R.string.stats_fastest_pace);
|
||||
|
||||
Pair<String, String> parts = StringUtils.getSpeedParts(getContext(), speed, preferenceMetricUnits, preferenceReportSpeed);
|
||||
Pair<String, String> parts = StringUtils.getSpeedParts(getContext(), trackStatistics.getMaxSpeed(), preferenceMetricUnits, preferenceReportSpeed);
|
||||
viewBinding.statsMaxSpeedValue.setText(parts.first);
|
||||
viewBinding.statsMaxSpeedUnit.setText(parts.second);
|
||||
}
|
||||
|
||||
// Set moving speed/pace
|
||||
{
|
||||
double speed = trackStatistics.getAverageMovingSpeed();
|
||||
|
||||
viewBinding.statsMovingSpeedLabel.setText(preferenceReportSpeed ? R.string.stats_average_moving_speed : R.string.stats_average_moving_pace);
|
||||
|
||||
Pair<String, String> parts = StringUtils.getSpeedParts(getContext(), speed, preferenceMetricUnits, preferenceReportSpeed);
|
||||
Pair<String, String> parts = StringUtils.getSpeedParts(getContext(), trackStatistics.getAverageMovingSpeed(), preferenceMetricUnits, preferenceReportSpeed);
|
||||
viewBinding.statsMovingSpeedValue.setText(parts.first);
|
||||
viewBinding.statsMovingSpeedUnit.setText(parts.second);
|
||||
}
|
||||
|
||||
@@ -24,7 +24,9 @@ import de.dennisguse.opentracks.TrackRecordingActivity;
|
||||
import de.dennisguse.opentracks.adapters.SensorsAdapter;
|
||||
import de.dennisguse.opentracks.content.TrackDataHub;
|
||||
import de.dennisguse.opentracks.content.TrackDataListener;
|
||||
import de.dennisguse.opentracks.content.data.Distance;
|
||||
import de.dennisguse.opentracks.content.data.Marker;
|
||||
import de.dennisguse.opentracks.content.data.Speed;
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
import de.dennisguse.opentracks.content.sensor.SensorDataCycling;
|
||||
@@ -371,18 +373,18 @@ public class StatisticsRecordingFragment extends Fragment implements TrackDataLi
|
||||
SensorDataCycling.DistanceSpeed data = sensorDataSet.getCyclingDistanceSpeed();
|
||||
|
||||
if (data.hasValue() && data.isRecent()) {
|
||||
setTotalDistance(data.getValue().distance_overall_m);
|
||||
setSpeed(data.getValue().speed_mps);
|
||||
setTotalDistance(data.getValue().distanceOverall);
|
||||
setSpeed(data.getValue().speed);
|
||||
}
|
||||
if (data.hasValue() && data.isRecent()) {
|
||||
setSpeed(data.getValue().speed_mps);
|
||||
setSpeed(data.getValue().speed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void updateStats() {
|
||||
|
||||
setTotalDistance(0);
|
||||
setTotalDistance(Distance.of(0)); //TODO Why?
|
||||
|
||||
// Set activity type
|
||||
{
|
||||
@@ -399,7 +401,7 @@ public class StatisticsRecordingFragment extends Fragment implements TrackDataLi
|
||||
|
||||
// Set average speed/pace
|
||||
{
|
||||
double speed = lastTrackStatistics != null ? lastTrackStatistics.getAverageSpeed() : Double.NaN;
|
||||
Speed speed = lastTrackStatistics != null ? lastTrackStatistics.getAverageSpeed() : null;
|
||||
viewBinding.statsAverageSpeedLabel.setText(preferenceReportSpeed ? R.string.stats_average_speed : R.string.stats_average_pace);
|
||||
|
||||
Pair<String, String> parts = StringUtils.getSpeedParts(getContext(), speed, preferenceMetricUnits, preferenceReportSpeed);
|
||||
@@ -409,7 +411,7 @@ public class StatisticsRecordingFragment extends Fragment implements TrackDataLi
|
||||
|
||||
// Set max speed/pace
|
||||
{
|
||||
double speed = lastTrackStatistics == null ? Double.NaN : lastTrackStatistics.getMaxSpeed();
|
||||
Speed speed = lastTrackStatistics != null ? lastTrackStatistics.getMaxSpeed() : null;
|
||||
|
||||
viewBinding.statsMaxSpeedLabel.setText(preferenceReportSpeed ? R.string.stats_max_speed : R.string.stats_fastest_pace);
|
||||
|
||||
@@ -420,7 +422,7 @@ public class StatisticsRecordingFragment extends Fragment implements TrackDataLi
|
||||
|
||||
// Set moving speed/pace
|
||||
{
|
||||
double speed = lastTrackStatistics != null ? lastTrackStatistics.getAverageMovingSpeed() : Double.NaN;
|
||||
Speed speed = lastTrackStatistics != null ? lastTrackStatistics.getAverageMovingSpeed() : null;
|
||||
|
||||
viewBinding.statsMovingSpeedLabel.setText(preferenceReportSpeed ? R.string.stats_average_moving_speed : R.string.stats_average_moving_pace);
|
||||
|
||||
@@ -449,7 +451,7 @@ public class StatisticsRecordingFragment extends Fragment implements TrackDataLi
|
||||
|
||||
private void setLocationValues() {
|
||||
// Set speed/pace
|
||||
double speed = lastTrackPoint != null && lastTrackPoint.hasSpeed() ? lastTrackPoint.getSpeed() : Double.NaN;
|
||||
Speed speed = lastTrackPoint != null && lastTrackPoint.hasSpeed() ? lastTrackPoint.getSpeed() : null;
|
||||
setSpeed(speed);
|
||||
|
||||
// Set altitude
|
||||
@@ -477,15 +479,15 @@ public class StatisticsRecordingFragment extends Fragment implements TrackDataLi
|
||||
}
|
||||
}
|
||||
|
||||
private void setTotalDistance(double sensorDistanceSinceLastTrackpoint) {
|
||||
double totalDistance = lastTrackStatistics != null ? (lastTrackStatistics.getTotalDistance() + sensorDistanceSinceLastTrackpoint) : Double.NaN;
|
||||
private void setTotalDistance(Distance sensorDistanceSinceLastTrackpoint) {
|
||||
Distance totalDistance = lastTrackStatistics != null ? (lastTrackStatistics.getTotalDistance().plus(sensorDistanceSinceLastTrackpoint)) : Distance.invalid();
|
||||
Pair<String, String> parts = StringUtils.getDistanceParts(getContext(), totalDistance, preferenceMetricUnits);
|
||||
|
||||
viewBinding.statsDistanceValue.setText(parts.first);
|
||||
viewBinding.statsDistanceUnit.setText(parts.second);
|
||||
}
|
||||
|
||||
private void setSpeed(double speed) {
|
||||
private void setSpeed(Speed speed) {
|
||||
viewBinding.statsSpeedLabel.setText(preferenceReportSpeed ? R.string.stats_speed : R.string.stats_pace);
|
||||
|
||||
Pair<String, String> parts = StringUtils.getSpeedParts(getContext(), speed, preferenceMetricUnits, preferenceReportSpeed);
|
||||
|
||||
@@ -290,7 +290,7 @@ public class GPXTrackExporter implements TrackExporter {
|
||||
printWriter.println("<extensions><gpxtpx:TrackPointExtension>");
|
||||
|
||||
if (trackPoint.hasSpeed()) {
|
||||
printWriter.println("<gpxtpx:speed>" + SPEED_FORMAT.format(trackPoint.getSpeed()) + "</gpxtpx:speed>");
|
||||
printWriter.println("<gpxtpx:speed>" + SPEED_FORMAT.format(trackPoint.getSpeed().toMPS()) + "</gpxtpx:speed>");
|
||||
}
|
||||
|
||||
if (trackPoint.hasHeartRate()) {
|
||||
|
||||
@@ -395,10 +395,10 @@ public class KMLTrackExporter implements TrackExporter {
|
||||
} else {
|
||||
printWriter.println("<gx:coord/>");
|
||||
}
|
||||
speedList.add(trackPoint.hasSpeed() ? trackPoint.getSpeed() : null);
|
||||
speedList.add(trackPoint.hasSpeed() ? (float) trackPoint.getSpeed().toMPS() : null);
|
||||
|
||||
if (exportSensorData) {
|
||||
distanceList.add(trackPoint.hasSensorDistance() ? trackPoint.getSensorDistance() : null);
|
||||
distanceList.add(trackPoint.hasSensorDistance() ? (float) trackPoint.getSensorDistance().toM() : null);
|
||||
heartRateList.add(trackPoint.hasHeartRate() ? trackPoint.getHeartRate_bpm() : null);
|
||||
cadenceList.add(trackPoint.hasCyclingCadence() ? trackPoint.getCyclingCadence_rpm() : null);
|
||||
powerList.add(trackPoint.hasPower() ? trackPoint.getPower() : null);
|
||||
|
||||
+12
-11
@@ -42,11 +42,14 @@ import javax.xml.parsers.ParserConfigurationException;
|
||||
import javax.xml.parsers.SAXParserFactory;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.content.data.Distance;
|
||||
import de.dennisguse.opentracks.content.data.Marker;
|
||||
import de.dennisguse.opentracks.content.data.Speed;
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
||||
import de.dennisguse.opentracks.content.provider.TrackPointIterator;
|
||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
import de.dennisguse.opentracks.stats.TrackStatisticsUpdater;
|
||||
import de.dennisguse.opentracks.util.FileUtils;
|
||||
import de.dennisguse.opentracks.util.LocationUtils;
|
||||
@@ -65,7 +68,7 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
|
||||
|
||||
private final Context context;
|
||||
private final ContentProviderUtils contentProviderUtils;
|
||||
private final int recordingDistanceInterval;
|
||||
private final Distance recordingDistanceInterval;
|
||||
|
||||
private final List<Track.Id> trackIds = new ArrayList<>();
|
||||
private final List<Marker> markers = new ArrayList<>();
|
||||
@@ -202,11 +205,10 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
|
||||
if (trackPoint.getLatitude() == marker.getLatitude() && trackPoint.getLongitude() == marker.getLongitude()) {
|
||||
String markerDescription = marker.getDescription();
|
||||
String icon = context.getString(R.string.marker_icon_url);
|
||||
double length = trackStatisticsUpdater.getTrackStatistics().getTotalDistance();
|
||||
Duration duration = trackStatisticsUpdater.getTrackStatistics().getTotalTime();
|
||||
TrackStatistics stats = trackStatisticsUpdater.getTrackStatistics();
|
||||
|
||||
// Insert marker
|
||||
Marker newMarker = new Marker(marker.getName(), markerDescription, marker.getCategory(), icon, track.getId(), length, duration, trackPoint, marker.getPhotoUrl());
|
||||
Marker newMarker = new Marker(marker.getName(), markerDescription, marker.getCategory(), icon, track.getId(), stats, trackPoint, marker.getPhotoUrl());
|
||||
contentProviderUtils.insertMarker(newMarker);
|
||||
}
|
||||
|
||||
@@ -374,17 +376,16 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
|
||||
* GPS points tend to have some inherent imprecision, speed and bearing will likely be off, so the statistics for things like max speed will also be off.
|
||||
*/
|
||||
if (trackPoint.hasLocation() && trackData.lastLocationInCurrentSegment.hasLocation()) {
|
||||
float speed = trackData.lastLocationInCurrentSegment.distanceToPrevious(trackPoint) / timeDifference.toMillis();
|
||||
trackPoint.setSpeed(speed);
|
||||
trackPoint.setSpeed(Speed.of(trackData.lastLocationInCurrentSegment.distanceToPrevious(trackPoint), timeDifference));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (trackPoint.hasLocation() && trackData.lastLocationInCurrentSegment.hasLocation()) {
|
||||
trackPoint.setBearing(trackData.lastLocationInCurrentSegment.bearingTo(trackPoint));
|
||||
|
||||
long maxRecordingDistance = PreferencesUtils.getMaxRecordingDistance(sharedPreferences, context); //TODO Should only be read once!
|
||||
double distanceToLastTrackLocation = trackPoint.distanceToPrevious(trackData.lastLocationInCurrentSegment);
|
||||
if (distanceToLastTrackLocation > maxRecordingDistance) {
|
||||
Distance maxRecordingDistance = PreferencesUtils.getMaxRecordingDistance(sharedPreferences, context); //TODO Should only be read once!
|
||||
Distance distanceToLastTrackLocation = trackPoint.distanceToPrevious(trackData.lastLocationInCurrentSegment);
|
||||
if (distanceToLastTrackLocation.greaterThan(maxRecordingDistance)) {
|
||||
trackPoint.setType(TrackPoint.Type.SEGMENT_START_AUTOMATIC);
|
||||
}
|
||||
}
|
||||
@@ -460,7 +461,7 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
|
||||
|
||||
if (speed != null) {
|
||||
try {
|
||||
trackPoint.setSpeed(Float.parseFloat(speed));
|
||||
trackPoint.setSpeed(Speed.of(speed));
|
||||
} catch (Exception e) {
|
||||
throw new ParsingException(createErrorMessage(String.format(Locale.US, "Unable to parse speed: %s", speed)), e);
|
||||
}
|
||||
@@ -505,7 +506,7 @@ abstract class AbstractFileTrackImporter extends DefaultHandler implements Track
|
||||
}
|
||||
if (distance != null) {
|
||||
try {
|
||||
trackPoint.setSensorDistance(Float.parseFloat(distance));
|
||||
trackPoint.setSensorDistance(Distance.of(distance));
|
||||
} catch (Exception e) {
|
||||
throw new ParsingException(createErrorMessage(String.format(Locale.US, "Unable to parse distance: %s", distance)), e);
|
||||
}
|
||||
|
||||
@@ -26,6 +26,8 @@ import org.xml.sax.SAXException;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.Distance;
|
||||
import de.dennisguse.opentracks.content.data.Speed;
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
||||
import de.dennisguse.opentracks.io.file.exporter.KMLTrackExporter;
|
||||
@@ -231,11 +233,11 @@ public class KmlFileTrackImporter extends AbstractFileTrackImporter {
|
||||
for (int i = 0; i < trackPoints.size(); i++) {
|
||||
TrackPoint trackPoint = trackPoints.get(i);
|
||||
|
||||
if (i < speedList.size()) {
|
||||
trackPoint.setSpeed(speedList.get(i));
|
||||
if (i < speedList.size() && speedList.get(i) != null) {
|
||||
trackPoint.setSpeed(Speed.of(speedList.get(i)));
|
||||
}
|
||||
if (i < distanceList.size()) {
|
||||
trackPoint.setSensorDistance(distanceList.get(i));
|
||||
if (i < distanceList.size() && distanceList.get(i) != null) {
|
||||
trackPoint.setSensorDistance(Distance.of(distanceList.get(i)));
|
||||
}
|
||||
if (i < heartRateList.size()) {
|
||||
trackPoint.setHeartRate_bpm(heartRateList.get(i));
|
||||
|
||||
@@ -40,6 +40,7 @@ import java.time.Instant;
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.TrackListActivity;
|
||||
import de.dennisguse.opentracks.TrackRecordingActivity;
|
||||
import de.dennisguse.opentracks.content.data.Distance;
|
||||
import de.dennisguse.opentracks.content.data.Marker;
|
||||
import de.dennisguse.opentracks.content.data.Track;
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
@@ -80,8 +81,8 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
|
||||
|
||||
private SharedPreferences sharedPreferences;
|
||||
|
||||
private int recordingDistanceInterval;
|
||||
private int maxRecordingDistance;
|
||||
private Distance recordingDistanceInterval;
|
||||
private Distance maxRecordingDistance;
|
||||
private final OnSharedPreferenceChangeListener sharedPreferenceChangeListener = new OnSharedPreferenceChangeListener() {
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
|
||||
@@ -237,11 +238,9 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
|
||||
photoUrl = photoUrl != null ? photoUrl : "";
|
||||
|
||||
TrackStatistics stats = trackStatisticsUpdater.getTrackStatistics();
|
||||
double length = stats.getTotalDistance();
|
||||
Duration duration = stats.getTotalTime();
|
||||
|
||||
// Insert marker
|
||||
Marker marker = new Marker(name, description, category, icon, serviceStatus.getRecordingTrackId(), length, duration, trackPoint, photoUrl);
|
||||
Marker marker = new Marker(name, description, category, icon, serviceStatus.getRecordingTrackId(), stats, trackPoint, photoUrl);
|
||||
Uri uri = contentProviderUtils.insertMarker(marker);
|
||||
return new Marker.Id(ContentUris.parseId(uri));
|
||||
}
|
||||
@@ -511,8 +510,8 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
|
||||
return;
|
||||
}
|
||||
|
||||
double distanceToLastTrackLocation = trackPoint.distanceToPrevious(lastValidTrackPoint);
|
||||
if (distanceToLastTrackLocation > maxRecordingDistance) {
|
||||
Distance distanceToLastTrackLocation = trackPoint.distanceToPrevious(lastValidTrackPoint);
|
||||
if (distanceToLastTrackLocation.greaterThan(maxRecordingDistance)) {
|
||||
insertTrackPointIfNewer(track, lastTrackPoint);
|
||||
|
||||
trackPoint.setType(TrackPoint.Type.SEGMENT_START_AUTOMATIC);
|
||||
@@ -523,7 +522,7 @@ public class TrackRecordingService extends Service implements HandlerServer.Hand
|
||||
return;
|
||||
}
|
||||
|
||||
if (trackPoint.hasSensorData() || distanceToLastTrackLocation >= recordingDistanceInterval) {
|
||||
if (trackPoint.hasSensorData() || distanceToLastTrackLocation.greaterOrEqualThan(recordingDistanceInterval)) {
|
||||
insertTrackPointIfNewer(track, lastTrackPoint);
|
||||
|
||||
insertTrackPoint(track, trackPoint);
|
||||
|
||||
+2
-1
@@ -11,6 +11,7 @@ import androidx.annotation.VisibleForTesting;
|
||||
import androidx.core.app.NotificationCompat;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.content.data.Distance;
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
import de.dennisguse.opentracks.util.StringUtils;
|
||||
@@ -68,7 +69,7 @@ class TrackRecordingServiceNotificationManager {
|
||||
void updateTrackPoint(Context context, TrackStatistics trackStatistics, TrackPoint trackPoint, int recordingGpsAccuracy) {
|
||||
String formattedAccuracy = context.getString(R.string.value_none);
|
||||
if (trackPoint.hasAccuracy()) {
|
||||
formattedAccuracy = StringUtils.formatDistance(context, trackPoint.getAccuracy(), metricUnits);
|
||||
formattedAccuracy = StringUtils.formatDistance(context, Distance.of(trackPoint.getAccuracy()), metricUnits);
|
||||
|
||||
boolean currentLocationWasAccurate = trackPoint.getAccuracy() < recordingGpsAccuracy;
|
||||
boolean shouldAlert = !currentLocationWasAccurate && previousLocationWasAccurate;
|
||||
|
||||
@@ -25,7 +25,7 @@ class GpsStatus {
|
||||
private static final Duration SIGNAL_LOST_THRESHOLD = Duration.ofSeconds(10);
|
||||
|
||||
// Threshold for accuracy.
|
||||
private double signalBadThreshold;
|
||||
private double signalBadThreshold; //TODO Distance?
|
||||
// Threshold for time without points.
|
||||
private Duration signalLostThreshold;
|
||||
|
||||
@@ -67,7 +67,7 @@ class GpsStatus {
|
||||
this.context = context;
|
||||
|
||||
SharedPreferences sharedPreferences = PreferencesUtils.getSharedPreferences(context);
|
||||
signalBadThreshold = PreferencesUtils.getRecordingDistanceInterval(sharedPreferences, context);
|
||||
signalBadThreshold = PreferencesUtils.getRecordingDistanceInterval(sharedPreferences, context).toM();
|
||||
|
||||
Duration minRecordingInterval = Duration.ofSeconds(PreferencesUtils.getMinRecordingInterval(sharedPreferences, context));
|
||||
signalLostThreshold = !minRecordingInterval.isNegative() ? SIGNAL_LOST_THRESHOLD.plus(minRecordingInterval) : SIGNAL_LOST_THRESHOLD;
|
||||
|
||||
@@ -78,7 +78,7 @@ class LocationHandler implements HandlerServer.Handler, LocationListener, GpsSta
|
||||
}
|
||||
if (PreferencesUtils.isKey(context, R.string.recording_distance_interval_key, key)) {
|
||||
if (gpsStatus != null) {
|
||||
gpsStatus.onRecordingDistanceChanged(PreferencesUtils.getRecordingDistanceInterval(sharedPreferences, context));
|
||||
gpsStatus.onRecordingDistanceChanged((int) PreferencesUtils.getRecordingDistanceInterval(sharedPreferences, context).toM()); //TODO Use Distance?
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,9 +20,9 @@ import android.util.Log;
|
||||
import java.time.Duration;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.content.data.Distance;
|
||||
import de.dennisguse.opentracks.services.TrackRecordingService;
|
||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
import de.dennisguse.opentracks.util.UnitConversions;
|
||||
|
||||
/**
|
||||
* Execute a periodic task on a time or distance schedule.
|
||||
@@ -128,12 +128,9 @@ public class PeriodicTaskExecutor {
|
||||
return;
|
||||
}
|
||||
|
||||
double distance = trackStatistics.getTotalDistance() * UnitConversions.M_TO_KM;
|
||||
if (!metricUnits) {
|
||||
distance *= UnitConversions.KM_TO_MI;
|
||||
}
|
||||
Distance distance = trackStatistics.getTotalDistance();
|
||||
|
||||
if (distance > nextTaskDistance) {
|
||||
if (distance.greaterThan(Distance.of(nextTaskDistance))) {
|
||||
periodicTask.run(trackRecordingService);
|
||||
calculateNextTaskDistance();
|
||||
}
|
||||
@@ -178,10 +175,7 @@ public class PeriodicTaskExecutor {
|
||||
return;
|
||||
}
|
||||
|
||||
double distance = trackStatistics.getTotalDistance() * UnitConversions.M_TO_KM;
|
||||
if (!metricUnits) {
|
||||
distance *= UnitConversions.KM_TO_MI;
|
||||
}
|
||||
double distance = trackStatistics.getTotalDistance().to(metricUnits);
|
||||
|
||||
// The index will be negative since the frequency is negative.
|
||||
int index = (int) (distance / taskFrequency);
|
||||
|
||||
@@ -32,7 +32,7 @@ final class PreferenceHelper {
|
||||
String[] entryValues = context.getResources().getStringArray(R.array.recording_distance_interval_values);
|
||||
String[] entries = new String[entryValues.length];
|
||||
|
||||
final int recordingDistanceIntervalDefault = PreferencesUtils.getRecordingDistanceIntervalDefault(context);
|
||||
final int recordingDistanceIntervalDefault = (int) PreferencesUtils.getRecordingDistanceIntervalDefault(context).toM();
|
||||
|
||||
for (int i = 0; i < entryValues.length; i++) {
|
||||
int value = Integer.parseInt(entryValues[i]);
|
||||
|
||||
@@ -22,6 +22,9 @@ import androidx.annotation.Nullable;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.Distance;
|
||||
import de.dennisguse.opentracks.content.data.Speed;
|
||||
|
||||
/**
|
||||
* Statistical data about a {@link de.dennisguse.opentracks.content.data.Track}.
|
||||
* The data in this class should be filled out by {@link TrackStatisticsUpdater}.
|
||||
@@ -40,13 +43,13 @@ public class TrackStatistics {
|
||||
// The track stop time.
|
||||
private Instant stopTime;
|
||||
|
||||
private double totalDistance_m;
|
||||
private Distance totalDistance;
|
||||
// Updated when new points are received, may be stale.
|
||||
private Duration totalTime;
|
||||
// Based on when we believe the user is traveling.
|
||||
private Duration movingTime;
|
||||
// The maximum speed (meters/second) that we believe is valid.
|
||||
private double maxSpeed_mps;
|
||||
private Speed maxSpeed;
|
||||
private Float totalAltitudeGain_m = null;
|
||||
private Float totalAltitudeLoss_m = null;
|
||||
|
||||
@@ -62,10 +65,10 @@ public class TrackStatistics {
|
||||
public TrackStatistics(TrackStatistics other) {
|
||||
startTime = other.startTime;
|
||||
stopTime = other.stopTime;
|
||||
totalDistance_m = other.totalDistance_m;
|
||||
totalDistance = other.totalDistance;
|
||||
totalTime = other.totalTime;
|
||||
movingTime = other.movingTime;
|
||||
maxSpeed_mps = other.maxSpeed_mps;
|
||||
maxSpeed = other.maxSpeed;
|
||||
altitudeExtremities.set(other.altitudeExtremities.getMin(), other.altitudeExtremities.getMax());
|
||||
totalAltitudeGain_m = other.totalAltitudeGain_m;
|
||||
totalAltitudeLoss_m = other.totalAltitudeLoss_m;
|
||||
@@ -89,10 +92,10 @@ public class TrackStatistics {
|
||||
stopTime = stopTime.isAfter(other.stopTime) ? stopTime : other.stopTime;
|
||||
}
|
||||
|
||||
totalDistance_m += other.totalDistance_m;
|
||||
totalDistance = totalDistance.plus(other.totalDistance);
|
||||
totalTime = totalTime.plus(other.totalTime);
|
||||
movingTime = movingTime.plus(other.movingTime);
|
||||
maxSpeed_mps = Math.max(maxSpeed_mps, other.maxSpeed_mps);
|
||||
maxSpeed = Speed.max(maxSpeed, other.maxSpeed);
|
||||
if (other.altitudeExtremities.hasData()) {
|
||||
altitudeExtremities.update(other.altitudeExtremities.getMin());
|
||||
altitudeExtremities.update(other.altitudeExtremities.getMax());
|
||||
@@ -121,10 +124,10 @@ public class TrackStatistics {
|
||||
startTime = null;
|
||||
stopTime = null;
|
||||
|
||||
setTotalDistance(0);
|
||||
setTotalDistance(Distance.of(0));
|
||||
setTotalTime(Duration.ofSeconds(0));
|
||||
setMovingTime(Duration.ofSeconds(0));
|
||||
setMaxSpeed(0);
|
||||
setMaxSpeed(Speed.zero());
|
||||
setTotalAltitudeGain(null);
|
||||
setTotalAltitudeLoss(null);
|
||||
}
|
||||
@@ -157,16 +160,16 @@ public class TrackStatistics {
|
||||
this.stopTime = stopTime;
|
||||
}
|
||||
|
||||
public double getTotalDistance() {
|
||||
return totalDistance_m;
|
||||
public Distance getTotalDistance() {
|
||||
return totalDistance;
|
||||
}
|
||||
|
||||
public void setTotalDistance(double totalDistance_m) {
|
||||
this.totalDistance_m = totalDistance_m;
|
||||
public void setTotalDistance(Distance totalDistance_m) {
|
||||
this.totalDistance = totalDistance_m;
|
||||
}
|
||||
|
||||
public void addTotalDistance(double distance_m) {
|
||||
totalDistance_m += distance_m;
|
||||
public void addTotalDistance(Distance distance_m) {
|
||||
totalDistance = totalDistance.plus(distance_m);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -195,35 +198,26 @@ public class TrackStatistics {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the average speed in meters/second.
|
||||
* Gets the average speed.
|
||||
* This calculation only takes into account the displacement until the last point that was accounted for in statistics.
|
||||
*/
|
||||
public double getAverageSpeed() {
|
||||
if (totalTime.isZero()) {
|
||||
return 0.0;
|
||||
public Speed getAverageSpeed() {
|
||||
if (totalDistance.isZero() && totalDistance.isZero()) {
|
||||
return Speed.of(0);
|
||||
}
|
||||
return totalDistance_m / (double) totalTime.getSeconds();
|
||||
return Speed.of(totalDistance.toM() / totalTime.getSeconds());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the average moving speed in meters/second.
|
||||
*/
|
||||
public double getAverageMovingSpeed() {
|
||||
if (movingTime.isZero()) {
|
||||
return 0.0;
|
||||
}
|
||||
return totalDistance_m / (double) movingTime.getSeconds();
|
||||
public Speed getAverageMovingSpeed() {
|
||||
return Speed.of(totalDistance, movingTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the maximum speed in meters/second.
|
||||
*/
|
||||
public double getMaxSpeed() {
|
||||
return Math.max(maxSpeed_mps, getAverageMovingSpeed());
|
||||
public Speed getMaxSpeed() {
|
||||
return Speed.max(maxSpeed, getAverageMovingSpeed());
|
||||
}
|
||||
|
||||
public void setMaxSpeed(double maxSpeed_mps) {
|
||||
this.maxSpeed_mps = maxSpeed_mps;
|
||||
public void setMaxSpeed(Speed maxSpeed) {
|
||||
this.maxSpeed = maxSpeed;
|
||||
}
|
||||
|
||||
public boolean hasAltitudeMin() {
|
||||
|
||||
@@ -23,8 +23,9 @@ import androidx.annotation.VisibleForTesting;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
import de.dennisguse.opentracks.content.data.Distance;
|
||||
import de.dennisguse.opentracks.content.data.Speed;
|
||||
import de.dennisguse.opentracks.content.data.TrackPoint;
|
||||
import de.dennisguse.opentracks.content.provider.TrackPointIterator;
|
||||
|
||||
/**
|
||||
* Updater for {@link TrackStatistics}.
|
||||
@@ -103,12 +104,12 @@ public class TrackStatisticsUpdater {
|
||||
* @param trackPoint the trackPoint
|
||||
* @param minGPSDistance the min recording distance
|
||||
*/
|
||||
public void addTrackPoint(TrackPoint trackPoint, int minGPSDistance) {
|
||||
public void addTrackPoint(TrackPoint trackPoint, Distance minGPSDistance) {
|
||||
internalAddTrackPoint(trackPoint, minGPSDistance);
|
||||
Log.d(TAG, this.toString());
|
||||
}
|
||||
|
||||
private void internalAddTrackPoint(TrackPoint trackPoint, int minGPSDistance) {
|
||||
private void internalAddTrackPoint(TrackPoint trackPoint, Distance minGPSDistance) {
|
||||
if (!trackInitialized) {
|
||||
trackStatistics.setStartTime(trackPoint.getTime());
|
||||
trackInitialized = true;
|
||||
@@ -158,8 +159,8 @@ public class TrackStatisticsUpdater {
|
||||
|
||||
if (!trackPoint.hasSensorDistance()) {
|
||||
// GPS-based distance/speed
|
||||
float movingDistance = lastMovingTrackPoint.distanceToPrevious(trackPoint);
|
||||
if (movingDistance < minGPSDistance && !trackPoint.isMoving()) {
|
||||
Distance movingDistance = lastMovingTrackPoint.distanceToPrevious(trackPoint);
|
||||
if (movingDistance.lessThan(minGPSDistance) && !trackPoint.isMoving()) {
|
||||
speedBuffer_mps.reset();
|
||||
lastTrackPoint = trackPoint;
|
||||
return;
|
||||
@@ -196,13 +197,6 @@ public class TrackStatisticsUpdater {
|
||||
speedBuffer_mps.reset();
|
||||
}
|
||||
|
||||
public void addTrackPoint(TrackPointIterator iterator, int minRecordingDistance) {
|
||||
while (iterator.hasNext()) {
|
||||
TrackPoint location = iterator.next();
|
||||
addTrackPoint(location, minRecordingDistance);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the smoothed altitude over several readings.
|
||||
* The altitude readings is noisy so the smoothed altitude is better than the raw altitude for many tasks.
|
||||
@@ -211,8 +205,8 @@ public class TrackStatisticsUpdater {
|
||||
return altitudeBuffer_m.getAverage();
|
||||
}
|
||||
|
||||
public double getSmoothedSpeed() {
|
||||
return speedBuffer_mps.getAverage();
|
||||
public Speed getSmoothedSpeed() {
|
||||
return Speed.of(speedBuffer_mps.getAverage());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -223,9 +217,10 @@ public class TrackStatisticsUpdater {
|
||||
if (!trackPoint.isMoving()) {
|
||||
speedBuffer_mps.reset();
|
||||
} else if (isValidSpeed(trackPoint, lastTrackPoint)) {
|
||||
speedBuffer_mps.setNext(trackPoint.getSpeed());
|
||||
if (speedBuffer_mps.getAverage() > currentSegment.getMaxSpeed()) {
|
||||
currentSegment.setMaxSpeed(speedBuffer_mps.getAverage());
|
||||
speedBuffer_mps.setNext(trackPoint.getSpeed().toMPS());
|
||||
Speed average = Speed.of(speedBuffer_mps.getAverage());
|
||||
if (average.greaterThan(currentSegment.getMaxSpeed())) {
|
||||
currentSegment.setMaxSpeed(average);
|
||||
}
|
||||
} else {
|
||||
Log.d(TAG, "Invalid speed. speed: " + trackPoint.getSpeed() + " lastLocationSpeed: " + lastTrackPoint.getSpeed());
|
||||
@@ -243,27 +238,26 @@ public class TrackStatisticsUpdater {
|
||||
|
||||
private boolean isValidSpeed(@NonNull TrackPoint trackPoint, @NonNull TrackPoint lastTrackPoint) {
|
||||
// There are a lot of noisy speed readings. Do the cheapest checks first, most expensive last.
|
||||
if (trackPoint.getSpeed() == 0) {
|
||||
if (trackPoint.getSpeed().isZero()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// The following code will ignore unlikely readings. 128 m/s seems to be an internal android error code.
|
||||
if (Math.abs(trackPoint.getSpeed() - 128) < 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// See if the speed seems physically likely. Ignore any speeds that imply acceleration greater than 2g.
|
||||
Duration timeDifference = Duration.between(lastTrackPoint.getTime(), trackPoint.getTime());
|
||||
double speedDifference = Math.abs(lastTrackPoint.getSpeed() - trackPoint.getSpeed());
|
||||
if (speedDifference > MAX_ACCELERATION * timeDifference.toMillis()) {
|
||||
return false;
|
||||
Speed maxAcceleration = Speed.of(MAX_ACCELERATION * timeDifference.toMillis());
|
||||
{
|
||||
// See if the speed seems physically likely. Ignore any speeds that imply acceleration greater than 2g.
|
||||
Speed speedDifference = Speed.absDiff(lastTrackPoint.getSpeed(), trackPoint.getSpeed());
|
||||
if (speedDifference.greaterThan(maxAcceleration)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Only check if the speed buffer is full. Check that the speed is less than 10X the smoothed average and the speed difference doesn't imply 2g acceleration.
|
||||
if (speedBuffer_mps.isFull()) {
|
||||
double average = speedBuffer_mps.getAverage();
|
||||
double diff = Math.abs(average - trackPoint.getSpeed());
|
||||
return (trackPoint.getSpeed() < average * 10) && (diff < MAX_ACCELERATION * timeDifference.toMillis());
|
||||
Speed average = Speed.of(speedBuffer_mps.getAverage());
|
||||
Speed speedDifference = Speed.absDiff(average, trackPoint.getSpeed());
|
||||
|
||||
return trackPoint.getSpeed().lessThan(average.mul(10)) && speedDifference.lessThan(maxAcceleration);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -7,6 +7,8 @@ import androidx.annotation.Nullable;
|
||||
import java.time.Duration;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.content.data.Distance;
|
||||
import de.dennisguse.opentracks.content.data.Speed;
|
||||
import de.dennisguse.opentracks.stats.TrackStatistics;
|
||||
import de.dennisguse.opentracks.viewmodels.IntervalStatistics;
|
||||
|
||||
@@ -16,17 +18,15 @@ public class AnnouncementUtils {
|
||||
}
|
||||
|
||||
public static String getAnnouncement(Context context, TrackStatistics trackStatistics, boolean isMetricUnits, boolean isReportSpeed, @Nullable IntervalStatistics.Interval currentInterval) {
|
||||
double distance = trackStatistics.getTotalDistance() * UnitConversions.M_TO_KM;
|
||||
double distancePerTime = trackStatistics.getAverageMovingSpeed() * UnitConversions.MPS_TO_KMH;
|
||||
double currentDistancePerTime = currentInterval != null ? currentInterval.getSpeed_ms() * UnitConversions.MPS_TO_KMH : 0;
|
||||
Distance distance = trackStatistics.getTotalDistance();
|
||||
Speed distancePerTime = trackStatistics.getAverageMovingSpeed();
|
||||
double currentDistancePerTime = currentInterval != null ? currentInterval.getSpeed_ms() * UnitConversions.MPS_TO_KMH : 0; //TODO Use Speed?
|
||||
|
||||
if (distance == 0) {
|
||||
if (distance.isZero()) {
|
||||
return context.getString(R.string.voice_total_distance_zero);
|
||||
}
|
||||
|
||||
if (!isMetricUnits) {
|
||||
distance *= UnitConversions.KM_TO_MI;
|
||||
distancePerTime *= UnitConversions.KM_TO_MI;
|
||||
currentDistancePerTime *= UnitConversions.KM_TO_MI;
|
||||
}
|
||||
|
||||
@@ -35,24 +35,27 @@ public class AnnouncementUtils {
|
||||
String currentRateMsg;
|
||||
if (isReportSpeed) {
|
||||
int speedId = isMetricUnits ? R.plurals.voiceSpeedKilometersPerHour : R.plurals.voiceSpeedMilesPerHour;
|
||||
rate = context.getResources().getQuantityString(speedId, getQuantityCount(distancePerTime), distancePerTime);
|
||||
double distanceInUnit = distancePerTime.to(isMetricUnits);
|
||||
rate = context.getResources().getQuantityString(speedId, getQuantityCount(distanceInUnit), distanceInUnit);
|
||||
|
||||
currentRate = context.getResources().getQuantityString(speedId, getQuantityCount(currentDistancePerTime), currentDistancePerTime);
|
||||
currentRateMsg = context.getString(R.string.voice_speed_lap, currentRate);
|
||||
} else {
|
||||
double timePerDistance = distancePerTime == 0 ? 0.0 : 1 / distancePerTime;
|
||||
double timePerDistance = distancePerTime.isZero() ? 0.0 : 1 / distancePerTime.to(isMetricUnits); //TODO PACE
|
||||
|
||||
int paceId = isMetricUnits ? R.string.voice_pace_per_kilometer : R.string.voice_pace_per_mile;
|
||||
Duration time = Duration.ofMillis((long) (timePerDistance * UnitConversions.HR_TO_MIN * UnitConversions.MIN_TO_S * UnitConversions.S_TO_MS));
|
||||
rate = context.getString(paceId, getAnnounceTime(context, time));
|
||||
|
||||
double currentTimePerDistance = currentDistancePerTime == 0 ? 0.0 : 1 / currentDistancePerTime;
|
||||
double currentTimePerDistance = currentDistancePerTime == 0 ? 0.0 : 1 / currentDistancePerTime; //TODO PACE
|
||||
Duration currentTime = Duration.ofMillis((long) (currentTimePerDistance * UnitConversions.HR_TO_MIN * UnitConversions.MIN_TO_S * UnitConversions.S_TO_MS));
|
||||
currentRate = context.getString(paceId, getAnnounceTime(context, currentTime));
|
||||
currentRateMsg = context.getString(R.string.voice_pace_lap, currentRate);
|
||||
}
|
||||
|
||||
int totalDistanceId = isMetricUnits ? R.plurals.voiceTotalDistanceKilometers : R.plurals.voiceTotalDistanceMiles;
|
||||
String totalDistance = context.getResources().getQuantityString(totalDistanceId, getQuantityCount(distance), distance);
|
||||
double distanceInUnit = distance.to(isMetricUnits);
|
||||
String totalDistance = context.getResources().getQuantityString(totalDistanceId, getQuantityCount(distanceInUnit), distanceInUnit);
|
||||
|
||||
currentRateMsg = currentInterval == null ? "" : " " + currentRateMsg;
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ import androidx.documentfile.provider.DocumentFile;
|
||||
import androidx.preference.PreferenceManager;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.content.data.Distance;
|
||||
import de.dennisguse.opentracks.io.file.TrackFileFormat;
|
||||
|
||||
/**
|
||||
@@ -180,17 +181,21 @@ public class PreferencesUtils {
|
||||
return getInt(sharedPreferences, context, R.string.voice_frequency_key, VOICE_FREQUENCY_DEFAULT);
|
||||
}
|
||||
|
||||
public static int getRecordingDistanceInterval(SharedPreferences sharedPreferences, Context context) {
|
||||
return getInt(sharedPreferences, context, R.string.recording_distance_interval_key, getRecordingDistanceIntervalDefault(context));
|
||||
public static Distance getRecordingDistanceInterval(SharedPreferences sharedPreferences, Context context) {
|
||||
return Distance.of(getInt(sharedPreferences, context, R.string.recording_distance_interval_key, getRecordingDistanceIntervalDefaultInternal(context)));
|
||||
}
|
||||
|
||||
public static int getRecordingDistanceIntervalDefault(Context context) {
|
||||
public static Distance getRecordingDistanceIntervalDefault(Context context) {
|
||||
return Distance.of(getRecordingDistanceIntervalDefaultInternal(context));
|
||||
}
|
||||
|
||||
private static int getRecordingDistanceIntervalDefaultInternal(Context context) {
|
||||
return Integer.parseInt(context.getResources().getString(R.string.recording_distance_interval_default));
|
||||
}
|
||||
|
||||
public static int getMaxRecordingDistance(SharedPreferences sharedPreferences, Context context) {
|
||||
public static Distance getMaxRecordingDistance(SharedPreferences sharedPreferences, Context context) {
|
||||
final int MAX_RECORDING_DISTANCE = Integer.parseInt(context.getResources().getString(R.string.max_recording_distance_default));
|
||||
return getInt(sharedPreferences, context, R.string.max_recording_distance_key, MAX_RECORDING_DISTANCE);
|
||||
return Distance.of(getInt(sharedPreferences, context, R.string.max_recording_distance_key, MAX_RECORDING_DISTANCE));
|
||||
}
|
||||
|
||||
//TODO Duration
|
||||
|
||||
@@ -36,6 +36,8 @@ import java.time.temporal.TemporalAccessor;
|
||||
import java.util.Locale;
|
||||
|
||||
import de.dennisguse.opentracks.R;
|
||||
import de.dennisguse.opentracks.content.data.Distance;
|
||||
import de.dennisguse.opentracks.content.data.Speed;
|
||||
|
||||
/**
|
||||
* Various string manipulation methods.
|
||||
@@ -86,50 +88,38 @@ public class StringUtils {
|
||||
* Formats the distance in meters.
|
||||
*
|
||||
* @param context the context
|
||||
* @param distance_m the distance_m
|
||||
* @param distance the distance
|
||||
* @param metricUnits true to use metric units. False to use imperial units
|
||||
*/
|
||||
public static String formatDistance(Context context, double distance_m, boolean metricUnits) {
|
||||
if (Double.isNaN(distance_m) || Double.isInfinite(distance_m)) {
|
||||
public static String formatDistance(Context context, Distance distance, boolean metricUnits) {
|
||||
if (distance.isInvalid()) {
|
||||
return context.getString(R.string.value_unknown);
|
||||
}
|
||||
|
||||
if (metricUnits) {
|
||||
if (distance_m > 500.0) {
|
||||
distance_m *= UnitConversions.M_TO_KM;
|
||||
return context.getString(R.string.value_float_kilometer, distance_m);
|
||||
if (distance.greaterThan(Distance.of(500))) {
|
||||
return context.getString(R.string.value_float_kilometer, distance.toKM());
|
||||
} else {
|
||||
return context.getString(R.string.value_float_meter, distance_m);
|
||||
return context.getString(R.string.value_float_meter, distance.toM());
|
||||
}
|
||||
} else {
|
||||
if (distance_m * UnitConversions.M_TO_MI > 0.5) {
|
||||
distance_m *= UnitConversions.M_TO_MI;
|
||||
return context.getString(R.string.value_float_mile, distance_m);
|
||||
if (distance.greaterThan(Distance.ofMile(0.5))) {
|
||||
return context.getString(R.string.value_float_mile, distance.toMI());
|
||||
} else {
|
||||
distance_m *= UnitConversions.M_TO_FT;
|
||||
return context.getString(R.string.value_float_feet, distance_m);
|
||||
return context.getString(R.string.value_float_feet, distance.toFT());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static String formatSpeed(Context context, double speed_mps, boolean metricUnits, boolean reportSpeed) {
|
||||
if (Double.isNaN(speed_mps) || Double.isInfinite(speed_mps)) {
|
||||
speed_mps = 0;
|
||||
}
|
||||
|
||||
double speed = speed_mps * UnitConversions.M_TO_KM; //KM p sec
|
||||
if (!metricUnits) {
|
||||
speed *= UnitConversions.KM_TO_MI;
|
||||
}
|
||||
|
||||
public static String formatSpeed(Context context, Speed speed, boolean metricUnits, boolean reportSpeed) {
|
||||
if (reportSpeed) {
|
||||
if (metricUnits) {
|
||||
return context.getString(R.string.value_float_kilometer_hour, speed * UnitConversions.S_TO_HR);
|
||||
return context.getString(R.string.value_float_kilometer_hour, speed.toKMH());
|
||||
} else {
|
||||
return context.getString(R.string.value_float_mile_hour, speed * UnitConversions.S_TO_HR);
|
||||
return context.getString(R.string.value_float_mile_hour, speed.toMPH());
|
||||
}
|
||||
} else {
|
||||
int pace = speed == 0 ? 0 : (int) Math.round(1 / speed); //sec / [KM | MI]
|
||||
int pace = (int) speed.toPace(metricUnits).getSeconds();
|
||||
int minutes = pace / 60;
|
||||
int seconds = pace % 60;
|
||||
if (metricUnits) {
|
||||
@@ -167,45 +157,40 @@ public class StringUtils {
|
||||
* Get the formatted distance with unit.
|
||||
*
|
||||
* @param context the context
|
||||
* @param distance_m the distance
|
||||
* @param distance the distance
|
||||
* @param metricUnits true to use metric unit
|
||||
* @return the formatted distance (or null) and it's unit as {@link Pair}
|
||||
*/
|
||||
public static Pair<String, String> getDistanceParts(Context context, double distance_m, boolean metricUnits) {
|
||||
if (Double.isNaN(distance_m) || Double.isInfinite(distance_m)) {
|
||||
public static Pair<String, String> getDistanceParts(Context context, Distance distance, boolean metricUnits) {
|
||||
if (distance.isInvalid()) {
|
||||
return new Pair<>(null, context.getString(metricUnits ? R.string.unit_meter : R.string.unit_feet));
|
||||
}
|
||||
|
||||
int unitId;
|
||||
if (metricUnits) {
|
||||
if (distance_m > 500.0) {
|
||||
distance_m *= UnitConversions.M_TO_KM;
|
||||
unitId = R.string.unit_kilometer;
|
||||
if (distance.greaterThan(Distance.of(500))) {
|
||||
return new Pair<>(formatDecimal(distance.toKM()), context.getString(R.string.unit_kilometer));
|
||||
} else {
|
||||
unitId = R.string.unit_meter;
|
||||
return new Pair<>(formatDecimal(distance.toM()), context.getString(R.string.unit_meter));
|
||||
}
|
||||
} else {
|
||||
if (distance_m * UnitConversions.M_TO_MI > 0.5) {
|
||||
distance_m *= UnitConversions.M_TO_MI;
|
||||
unitId = R.string.unit_mile;
|
||||
if (distance.greaterThan(Distance.of(0.5 * UnitConversions.M_TO_MI))) {
|
||||
return new Pair<>(formatDecimal(distance.toMI()), context.getString(R.string.unit_mile));
|
||||
} else {
|
||||
distance_m *= UnitConversions.M_TO_FT;
|
||||
unitId = R.string.unit_feet;
|
||||
return new Pair<>(formatDecimal(distance.toFT()), context.getString(R.string.unit_feet));
|
||||
}
|
||||
}
|
||||
return new Pair<>(formatDecimal(distance_m), context.getString(unitId));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the formatted speed with unit.
|
||||
*
|
||||
* @param context the context
|
||||
* @param speed_mps the speed
|
||||
* @param speed the speed
|
||||
* @param metricUnits true to use metric unit
|
||||
* @param reportSpeed true to report speed; false for pace
|
||||
* @return the formatted speed (or null) and it's unit as {@link Pair}
|
||||
*/
|
||||
public static Pair<String, String> getSpeedParts(Context context, double speed_mps, boolean metricUnits, boolean reportSpeed) {
|
||||
public static Pair<String, String> getSpeedParts(Context context, Speed speed, boolean metricUnits, boolean reportSpeed) {
|
||||
int unitId;
|
||||
if (metricUnits) {
|
||||
unitId = reportSpeed ? R.string.unit_kilometer_per_hour : R.string.unit_minute_per_kilometer;
|
||||
@@ -214,21 +199,15 @@ public class StringUtils {
|
||||
}
|
||||
String unitString = context.getString(unitId);
|
||||
|
||||
|
||||
if (Double.isNaN(speed_mps) || Double.isInfinite(speed_mps)) {
|
||||
speed_mps = 0;
|
||||
}
|
||||
|
||||
double speed = speed_mps * UnitConversions.M_TO_KM; //KM p sec
|
||||
if (!metricUnits) {
|
||||
speed *= UnitConversions.KM_TO_MI;
|
||||
if (speed == null) {
|
||||
speed = Speed.zero();
|
||||
}
|
||||
|
||||
if (reportSpeed) {
|
||||
return new Pair<>(StringUtils.formatDecimal(speed * UnitConversions.S_TO_HR, 1), unitString);
|
||||
return new Pair<>(StringUtils.formatDecimal(speed.to(metricUnits), 1), unitString);
|
||||
}
|
||||
|
||||
int pace = speed == 0 ? 0 : (int) Math.round(1 / speed); //sec / [KM | MI]
|
||||
int pace = (int) speed.toPace(metricUnits).getSeconds();
|
||||
|
||||
int minutes = pace / 60;
|
||||
int seconds = pace % 60;
|
||||
|
||||
@@ -49,7 +49,7 @@ public class UnitConversions {
|
||||
|
||||
// Distance
|
||||
// multiplication factor to convert miles to feet
|
||||
public static final double MI_TO_FT = 5280.0;
|
||||
private static final double MI_TO_FT = 5280.0;
|
||||
// multiplication factor to convert feet to miles
|
||||
public static final double FT_TO_MI = 1 / MI_TO_FT;
|
||||
// multiplication factor to covert kilometers to meters
|
||||
|
||||
@@ -33,7 +33,7 @@ public class IntervalStatistics {
|
||||
TrackPoint trackPoint = trackPoints.get(i);
|
||||
|
||||
if (trackPoint.hasLocation() && prevTrackPoint.hasLocation()) {
|
||||
interval.distance_m += prevTrackPoint.distanceToPrevious(trackPoint);
|
||||
interval.distance_m += prevTrackPoint.distanceToPrevious(trackPoint).toM();
|
||||
interval.time = interval.time.plus(Duration.between(prevTrackPoint.getTime(), trackPoint.getTime()));
|
||||
interval.gain_m += trackPoint.hasAltitudeGain() ? trackPoint.getAltitudeGain() : 0;
|
||||
interval.loss_m += trackPoint.hasAltitudeLoss() ? trackPoint.getAltitudeLoss() : 0;
|
||||
|
||||
Reference in New Issue
Block a user