Use distance from cycling speed sensors.

Fixes #505.
This commit is contained in:
Dennis Guse
2021-02-03 19:50:23 +01:00
parent 15fbd90ca4
commit a5158a55d9
28 changed files with 502 additions and 202 deletions
@@ -57,6 +57,7 @@ public class TrackPoint {
private Double altitude_m;
private Float speed_mps;
private Float bearing;
private Float sensorDistance_m;
public enum Type {
SEGMENT_START_MANUAL(-2), //Start of a segment due to user interaction (start, resume)
@@ -224,7 +225,7 @@ public class TrackPoint {
return elevationGain != null;
}
public Float getElevationGain() {
public float getElevationGain() {
return elevationGain;
}
@@ -310,8 +311,12 @@ public class TrackPoint {
this.accuracy = horizontalAccuracy;
}
public float distanceTo(@NonNull TrackPoint dest) {
return getLocation().distanceTo(dest.getLocation());
public float distanceToPrevious(@NonNull TrackPoint previous) {
if (hasSensorDistance()) {
return getSensorDistance();
}
return getLocation().distanceTo(previous.getLocation());
}
public boolean fulfillsAccuracy(int poorAccuracy) {
@@ -327,6 +332,18 @@ public class TrackPoint {
}
// Sensor data
public boolean hasSensorDistance() {
return sensorDistance_m != null;
}
public Float getSensorDistance() {
return sensorDistance_m;
}
public void setSensorDistance(Float distance_m) {
this.sensorDistance_m = distance_m;
}
public boolean hasSensorData() {
return hasHeartRate() || hasCyclingCadence() || hasPower();
}
@@ -347,7 +364,7 @@ public class TrackPoint {
return cyclingCadence_rpm != null;
}
public Float getCyclingCadence_rpm() {
public float getCyclingCadence_rpm() {
return cyclingCadence_rpm;
}
@@ -359,7 +376,7 @@ public class TrackPoint {
return power != null;
}
public Float getPower() {
public float getPower() {
return power;
}
@@ -50,6 +50,7 @@ public interface TrackPointsColumns extends BaseColumns {
String BEARING = "bearing";
String SENSOR_HEARTRATE = "sensor_heartrate";
String SENSOR_CADENCE = "sensor_cadence";
String SENSOR_DISTANCE = "sensor_distance"; //DISTANCE from previous TrackPoint
String SENSOR_POWER = "sensor_power";
String ELEVATION_GAIN = "elevation_gain";
String ELEVATION_LOSS = "elevation_loss";
@@ -77,6 +78,7 @@ public interface TrackPointsColumns extends BaseColumns {
+ ELEVATION_GAIN + " FLOAT, "
+ ELEVATION_LOSS + " FLOAT, "
+ TYPE + " TEXT CHECK(type IN (-2, -1, 0, 1)), "
+ SENSOR_DISTANCE + " FLOAT, "
+ "FOREIGN KEY (" + TRACKID + ") REFERENCES " + TracksColumns.TABLE_NAME + "(" + TracksColumns._ID + ") ON UPDATE CASCADE ON DELETE CASCADE"
+ ")";
@@ -19,6 +19,7 @@ class CachedTrackPointsIndexes {
final int bearingIndex;
final int sensorHeartRateIndex;
final int sensorCadenceIndex;
final int sensorDistanceIndex;
final int sensorPowerIndex;
final int elevationGainIndex;
final int elevationLossIndex;
@@ -35,6 +36,7 @@ class CachedTrackPointsIndexes {
bearingIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.BEARING);
sensorHeartRateIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.SENSOR_HEARTRATE);
sensorCadenceIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.SENSOR_CADENCE);
sensorDistanceIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.SENSOR_DISTANCE);
sensorPowerIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.SENSOR_POWER);
elevationGainIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.ELEVATION_GAIN);
elevationLossIndex = cursor.getColumnIndexOrThrow(TrackPointsColumns.ELEVATION_LOSS);
@@ -556,6 +556,9 @@ public class ContentProviderUtils {
if (!cursor.isNull(indexes.sensorCadenceIndex)) {
trackPoint.setCyclingCadence_rpm(cursor.getFloat(indexes.sensorCadenceIndex));
}
if (!cursor.isNull(indexes.sensorDistanceIndex)) {
trackPoint.setSensorDistance(cursor.getFloat(indexes.sensorDistanceIndex));
}
if (!cursor.isNull(indexes.sensorPowerIndex)) {
trackPoint.setPower(cursor.getFloat(indexes.sensorPowerIndex));
}
@@ -708,6 +711,9 @@ public class ContentProviderUtils {
if (trackPoint.hasCyclingCadence()) {
values.put(TrackPointsColumns.SENSOR_CADENCE, trackPoint.getCyclingCadence_rpm());
}
if (trackPoint.hasSensorDistance()) {
values.put(TrackPointsColumns.SENSOR_DISTANCE, trackPoint.getSensorDistance());
}
if (trackPoint.hasPower()) {
values.put(TrackPointsColumns.SENSOR_POWER, trackPoint.getPower());
}
@@ -25,7 +25,7 @@ public class CustomSQLiteOpenHelper extends SQLiteOpenHelper {
private static final String TAG = CustomSQLiteOpenHelper.class.getSimpleName();
private static final int DATABASE_VERSION = 30;
private static final int DATABASE_VERSION = 31;
@VisibleForTesting
public static final String DATABASE_NAME = "database.db";
@@ -82,6 +82,9 @@ public class CustomSQLiteOpenHelper extends SQLiteOpenHelper {
case 30:
upgradeFrom29to30(db);
break;
case 31:
upgradeFrom30to31(db);
break;
default:
throw new RuntimeException("Not implemented: upgrade to " + toVersion);
@@ -115,7 +118,9 @@ public class CustomSQLiteOpenHelper extends SQLiteOpenHelper {
case 29:
downgradeFrom30to29(db);
break;
case 30:
downgradeFrom31to30(db);
break;
default:
throw new RuntimeException("Not implemented: downgrade to " + toVersion);
}
@@ -394,4 +399,32 @@ public class CustomSQLiteOpenHelper extends SQLiteOpenHelper {
db.setTransactionSuccessful();
db.endTransaction();
}
/**
* Add distance column to TrackPoint.
*/
private void upgradeFrom30to31(SQLiteDatabase db) {
db.beginTransaction();
// TrackPoints
db.execSQL("ALTER TABLE trackpoints ADD COLUMN sensor_distance FLOAT");
db.setTransactionSuccessful();
db.endTransaction();
}
private void downgradeFrom31to30(SQLiteDatabase db) {
db.beginTransaction();
// TrackPoints; identical to upgradeFrom27to28()
db.execSQL("ALTER TABLE trackpoints RENAME TO trackpoints_old");
db.execSQL("CREATE TABLE trackpoints (_id INTEGER PRIMARY KEY AUTOINCREMENT, trackid INTEGER NOT NULL, longitude INTEGER, latitude INTEGER, time INTEGER, elevation FLOAT, accuracy FLOAT, speed FLOAT, bearing FLOAT, sensor_heartrate FLOAT, sensor_cadence FLOAT, sensor_power FLOAT, elevation_gain FLOAT, elevation_loss FLOAT, type TEXT CHECK(type IN (-2, -1, 0, 1)), FOREIGN KEY (trackid) REFERENCES tracks(_id) ON UPDATE CASCADE ON DELETE CASCADE)");
db.execSQL("INSERT INTO trackpoints SELECT _id, trackid, longitude, latitude, time, elevation, accuracy, speed, bearing, sensor_heartrate, sensor_cadence, sensor_power, elevation_gain, elevation_gain, type FROM trackpoints_old");
db.execSQL("DROP TABLE trackpoints_old");
db.execSQL("CREATE INDEX trackpoints_trackid_index ON trackpoints(trackid)");
db.setTransactionSuccessful();
db.endTransaction();
}
}
@@ -54,6 +54,12 @@ public abstract class SensorData<T> {
return value;
}
/**
* Reset long term aggregated values (more than derived from previous SensorData). e.g. overall distance.
*/
public void reset() {
}
/**
* Is the data recent considering the current time.
*/
@@ -61,4 +67,10 @@ public abstract class SensorData<T> {
return Instant.now()
.isBefore(time.plus(BluetoothRemoteSensorManager.MAX_SENSOR_DATE_SET_AGE_MS));
}
@NonNull
@Override
public String toString() {
return "sensorAddress='" + sensorAddress;
}
}
@@ -41,7 +41,7 @@ public final class SensorDataCycling {
/**
* Workaround for Wahoo CADENCE: provides speed instead of cadence
*/
public Cadence(@NonNull SensorDataCycling.Speed speed) {
public Cadence(@NonNull DistanceSpeed speed) {
this(speed.getSensorAddress(), speed.getSensorName(), speed.wheelRevolutionsCount, speed.wheelRevolutionsTime);
}
@@ -74,7 +74,7 @@ public final class SensorDataCycling {
@NonNull
@Override
public String toString() {
return "cadence=" + value + " time=" + crankRevolutionsTime + " count=" + crankRevolutionsCount;
return super.toString() + " cadence=" + value + " time=" + crankRevolutionsTime + " count=" + crankRevolutionsCount;
}
@Override
@@ -90,18 +90,18 @@ public final class SensorDataCycling {
}
}
public static class Speed extends SensorData<Float> {
public static class DistanceSpeed extends SensorData<DistanceSpeed.Data> {
private final Integer wheelRevolutionsCount; // UINT16
private final Integer wheelRevolutionsTime; // UINT16; 1/1024s
public Speed(String sensorAddress) {
public DistanceSpeed(String sensorAddress) {
super(sensorAddress);
this.wheelRevolutionsCount = null;
this.wheelRevolutionsTime = null;
}
public Speed(String sensorAddress, String sensorName, int wheelRevolutionsCount, int wheelRevolutionsTime) {
public DistanceSpeed(String sensorAddress, String sensorName, int wheelRevolutionsCount, int wheelRevolutionsTime) {
super(sensorAddress, sensorName);
this.wheelRevolutionsCount = wheelRevolutionsCount;
this.wheelRevolutionsTime = wheelRevolutionsTime;
@@ -119,7 +119,7 @@ public final class SensorDataCycling {
return wheelRevolutionsTime;
}
public void compute(Speed previous, int wheel_circumference_mm) {
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) {
@@ -128,42 +128,76 @@ public final class SensorDataCycling {
} else {
long wheelDiff = UintUtils.diff(wheelRevolutionsCount, previous.wheelRevolutionsCount, UintUtils.UINT16_MAX);
double timeDiff_s = timeDiff_ms * UnitConversions.MS_TO_S;
value = (float) (wheelDiff * wheel_circumference_mm * UnitConversions.MM_TO_M / timeDiff_s);
float distance_m = (float) (wheelDiff * wheel_circumference_mm * UnitConversions.MM_TO_M);
float distance_overall_m = distance_m;
if (previous.hasValue()) {
distance_overall_m += previous.getValue().distance_overall_m;
}
float speed_mps = (float) (distance_m / timeDiff_s);
value = new Data(distance_m, distance_overall_m, speed_mps);
}
}
}
@Override
public void reset() {
if (value != null) {
value = new Data(value.distance_m, 0, value.speed_mps);
}
}
@NonNull
@Override
public String toString() {
return "speed=" + value + " time=" + wheelRevolutionsTime + " count=" + wheelRevolutionsCount;
return super.toString() + " data=" + value + " time=" + wheelRevolutionsTime + " count=" + wheelRevolutionsCount;
}
@Override
public boolean equals(@Nullable Object obj) {
if (!(obj instanceof Speed)) return false;
if (!(obj instanceof DistanceSpeed)) return false;
Speed comp = (Speed) obj;
DistanceSpeed comp = (DistanceSpeed) obj;
if (hasData() && comp.hasData() == hasData()) {
return getWheelRevolutionsCount() == comp.getWheelRevolutionsCount() && getWheelRevolutionsTime() == comp.getWheelRevolutionsTime();
} else {
return false;
}
}
public static class Data {
public final float distance_m;
public final float distance_overall_m;
public final float speed_mps;
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;
}
@Override
public String toString() {
return "Data{" +
"distance_m=" + distance_m +
", distance_overall_m=" + distance_overall_m +
", speed_mps=" + speed_mps +
'}';
}
}
}
public static class CadenceAndSpeed extends SensorData<Pair<Cadence, Speed>> {
public static class CadenceAndSpeed extends SensorData<Pair<Cadence, DistanceSpeed>> {
public CadenceAndSpeed(String sensorAddress, String sensorName, @NonNull Cadence cadence, @NonNull Speed speed) {
public CadenceAndSpeed(String sensorAddress, String sensorName, @Nullable Cadence cadence, @Nullable DistanceSpeed distanceSpeed) {
super(sensorAddress, sensorName);
this.value = new Pair<>(cadence, speed);
this.value = new Pair<>(cadence, distanceSpeed);
}
public Cadence getCadence() {
return this.value != null ? this.value.first : null;
}
public Speed getSpeed() {
public DistanceSpeed getDistanceSpeed() {
return this.value != null ? this.value.second : null;
}
}
@@ -16,6 +16,6 @@ public class SensorDataCyclingPower extends SensorData<Float> {
@NonNull
@Override
public String toString() {
return "power=" + value;
return super.toString() + " power=" + value;
}
}
@@ -16,6 +16,6 @@ public class SensorDataHeartRate extends SensorData<Float> {
@NonNull
@Override
public String toString() {
return "heart=" + value;
return super.toString() + " heart=" + value;
}
}
@@ -10,7 +10,7 @@ public final class SensorDataSet {
private SensorDataCycling.Cadence cyclingCadence;
private SensorDataCycling.Speed cyclingSpeed;
private SensorDataCycling.DistanceSpeed cyclingDistanceSpeed;
private SensorDataCyclingPower cyclingPower;
@@ -25,8 +25,8 @@ public final class SensorDataSet {
return cyclingCadence;
}
public SensorDataCycling.Speed getCyclingSpeed() {
return cyclingSpeed;
public SensorDataCycling.DistanceSpeed getCyclingDistanceSpeed() {
return cyclingDistanceSpeed;
}
public SensorDataCyclingPower getCyclingPower() {
@@ -44,7 +44,7 @@ public final class SensorDataSet {
public void clear() {
this.heartRate = null;
this.cyclingCadence = null;
this.cyclingSpeed = null;
this.cyclingDistanceSpeed = null;
this.cyclingPower = null;
}
@@ -57,8 +57,9 @@ public final class SensorDataSet {
trackPoint.setCyclingCadence_rpm(cyclingCadence.getValue());
}
if (cyclingSpeed != null && cyclingSpeed.hasValue()) {
trackPoint.setSpeed(cyclingSpeed.getValue());
if (cyclingDistanceSpeed != null && cyclingDistanceSpeed.hasValue()) {
trackPoint.setSensorDistance(cyclingDistanceSpeed.getValue().distance_overall_m);
trackPoint.setSpeed(cyclingDistanceSpeed.getValue().speed_mps);
}
if (cyclingPower != null && cyclingPower.hasValue()) {
@@ -66,12 +67,19 @@ public final class SensorDataSet {
}
}
public void reset() {
if (heartRate != null) heartRate.reset();
if (cyclingCadence != null) cyclingCadence.reset();
if (cyclingDistanceSpeed != null) cyclingDistanceSpeed.reset();
if (cyclingPower != null) cyclingPower.reset();
}
@NonNull
@Override
public String toString() {
return (getHeartRate() != null ? "" + getHeartRate() : "")
+ (getCyclingCadence() != null ? " " + getCyclingCadence() : "")
+ (getCyclingSpeed() != null ? " " + getCyclingSpeed() : "")
+ (getCyclingDistanceSpeed() != null ? " " + getCyclingDistanceSpeed() : "")
+ (getCyclingPower() != null ? " " + getCyclingPower() : "");
}
@@ -85,8 +93,8 @@ public final class SensorDataSet {
this.cyclingCadence = (SensorDataCycling.Cadence) data;
return;
}
if (type instanceof SensorDataCycling.Speed) {
this.cyclingSpeed = (SensorDataCycling.Speed) data;
if (type instanceof SensorDataCycling.DistanceSpeed) {
this.cyclingDistanceSpeed = (SensorDataCycling.DistanceSpeed) data;
return;
}