support for lap cycling power in the announcer

Implements #1806
This commit is contained in:
Peter Hercek
2023-12-31 14:59:33 +01:00
committed by Dennis Guse
parent 70a2040142
commit 6cfc05af82
7 changed files with 103 additions and 18 deletions
@@ -4,6 +4,7 @@ import static android.text.Spanned.SPAN_INCLUSIVE_EXCLUSIVE;
import static de.dennisguse.opentracks.settings.PreferencesUtils.shouldVoiceAnnounceAverageHeartRate;
import static de.dennisguse.opentracks.settings.PreferencesUtils.shouldVoiceAnnounceAverageSpeedPace;
import static de.dennisguse.opentracks.settings.PreferencesUtils.shouldVoiceAnnounceLapHeartRate;
import static de.dennisguse.opentracks.settings.PreferencesUtils.shouldVoiceAnnounceLapPower;
import static de.dennisguse.opentracks.settings.PreferencesUtils.shouldVoiceAnnounceLapSpeedPace;
import static de.dennisguse.opentracks.settings.PreferencesUtils.shouldVoiceAnnounceMovingTime;
import static de.dennisguse.opentracks.settings.PreferencesUtils.shouldVoiceAnnounceTotalDistance;
@@ -23,6 +24,7 @@ import java.util.Map;
import de.dennisguse.opentracks.R;
import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.Power;
import de.dennisguse.opentracks.data.models.Speed;
import de.dennisguse.opentracks.settings.UnitSystem;
import de.dennisguse.opentracks.stats.SensorStatistics;
@@ -159,6 +161,19 @@ class VoiceAnnouncementUtils {
builder.append(".");
}
if (shouldVoiceAnnounceLapPower() && currentInterval != null && currentInterval.hasAveragePower()) {
int currentPower = Math.round(currentInterval.getAveragePower().getW());
if(shouldVoiceAnnounceUnit()) {
String template = context.getResources().getString(R.string.power_x_watt);
builder.append(" ")
.append(MessageFormat.format(template, Map.of("x", currentPower)));
} else {
builder.append(" ").append(context.getString(R.string.power)).append(" ");
builder.append(String.valueOf(currentPower));
}
builder.append(".");
}
return builder;
}
@@ -434,6 +434,10 @@ public class PreferencesUtils {
return getBoolean(R.string.voice_announce_lap_heart_rate_key, false);
}
public static boolean shouldVoiceAnnounceLapPower() {
return getBoolean(R.string.voice_announce_lap_power_key, false);
}
@VisibleForTesting
public static void setVoiceAnnounceLapHeartRate(boolean value) {
setBoolean(R.string.voice_announce_lap_heart_rate_key, value);
@@ -26,6 +26,7 @@ import java.time.Instant;
import de.dennisguse.opentracks.data.models.Altitude;
import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.HeartRate;
import de.dennisguse.opentracks.data.models.Power;
import de.dennisguse.opentracks.data.models.Speed;
import de.dennisguse.opentracks.data.models.Track;
import de.dennisguse.opentracks.data.models.TrackPoint;
@@ -60,6 +61,7 @@ public class TrackStatistics {
private Float totalAltitudeLoss_m = null;
// The average heart rate seen on this track
private HeartRate avgHeartRate = null;
private Power avgPower = null;
private boolean isIdle;
@@ -83,6 +85,7 @@ public class TrackStatistics {
totalAltitudeGain_m = other.totalAltitudeGain_m;
totalAltitudeLoss_m = other.totalAltitudeLoss_m;
avgHeartRate = other.avgHeartRate;
avgPower = other.avgPower;
isIdle = other.isIdle;
}
@@ -129,6 +132,19 @@ public class TrackStatistics {
}
}
if (avgPower == null) {
avgPower = other.avgPower;
} else {
if (other.avgPower != null) {
// Using total time as weights for the averaging.
// Important to do this before total time is updated
avgPower = Power.of(
(totalTime.getSeconds() * avgPower.getW() + other.totalTime.getSeconds() * other.avgPower.getW())
/ (totalTime.getSeconds() + other.totalTime.getSeconds())
);
}
}
totalDistance = totalDistance.plus(other.totalDistance);
totalTime = totalTime.plus(other.totalTime);
movingTime = movingTime.plus(other.movingTime);
@@ -270,6 +286,15 @@ public class TrackStatistics {
return avgHeartRate;
}
public boolean hasPower() {
return avgPower != null;
}
@Nullable
public Power getAveragePower() {
return avgPower;
}
/**
* Gets the average speed.
* This calculation only takes into account the displacement until the last point that was accounted for in statistics.
@@ -333,6 +358,12 @@ public class TrackStatistics {
}
}
public void setAveragePower(Power power) {
if (power != null) {
avgPower = power;
}
}
public boolean hasTotalAltitudeGain() {
return totalAltitudeGain_m != null;
}
@@ -23,6 +23,7 @@ import java.util.List;
import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.HeartRate;
import de.dennisguse.opentracks.data.models.Power;
import de.dennisguse.opentracks.data.models.Speed;
import de.dennisguse.opentracks.data.models.TrackPoint;
@@ -43,6 +44,8 @@ public class TrackStatisticsUpdater {
private float averageHeartRateBPM;
private Duration totalHeartRateDuration = Duration.ZERO;
private float averagePowerW;
private Duration totalPowerDuration = Duration.ZERO;
// The current segment's statistics
private final TrackStatistics currentSegment;
@@ -125,6 +128,17 @@ public class TrackStatisticsUpdater {
currentSegment.setAverageHeartRate(HeartRate.of(averageHeartRateBPM));
}
// Update power
if (trackPoint.hasPower() && lastTrackPoint != null) {
Duration trackPointDuration = Duration.between(lastTrackPoint.getTime(), trackPoint.getTime());
Duration newTotalDuration = totalPowerDuration.plus(trackPointDuration);
averagePowerW = (totalPowerDuration.toMillis() * averagePowerW + trackPointDuration.toMillis() * trackPoint.getPower().getW()) / newTotalDuration.toMillis();
totalPowerDuration = newTotalDuration;
currentSegment.setAveragePower(Power.of(averagePowerW));
}
{
// Update total distance
Distance movingDistance = null;
@@ -9,6 +9,7 @@ import java.util.List;
import de.dennisguse.opentracks.data.TrackPointIterator;
import de.dennisguse.opentracks.data.models.Distance;
import de.dennisguse.opentracks.data.models.HeartRate;
import de.dennisguse.opentracks.data.models.Power;
import de.dennisguse.opentracks.data.models.Speed;
import de.dennisguse.opentracks.data.models.TrackPoint;
import de.dennisguse.opentracks.stats.TrackStatistics;
@@ -103,6 +104,7 @@ public class IntervalStatistics {
private Float gain_m;
private Float loss_m;
private HeartRate avgHeartRate;
private Power avgPower;
public Interval() {
}
@@ -118,6 +120,7 @@ public class IntervalStatistics {
gain_m = i.gain_m;
loss_m = i.loss_m;
avgHeartRate = i.avgHeartRate;
avgPower = i.avgPower;
}
public Interval(Interval i) {
@@ -126,6 +129,7 @@ public class IntervalStatistics {
gain_m = i.gain_m;
loss_m = i.loss_m;
avgHeartRate = i.avgHeartRate;
avgPower = i.avgPower;
}
public Distance getDistance() {
@@ -160,12 +164,21 @@ public class IntervalStatistics {
return avgHeartRate;
}
public boolean hasAveragePower() {
return avgPower != null;
}
public Power getAveragePower() {
return avgPower;
}
private void add(TrackStatistics trackStatistics, @Nullable TrackPoint lastTrackPoint) {
distance = distance.plus(trackStatistics.getTotalDistance());
time = time.plus(trackStatistics.getTotalTime());
gain_m = trackStatistics.hasTotalAltitudeGain() ? trackStatistics.getTotalAltitudeGain() : gain_m;
loss_m = trackStatistics.hasTotalAltitudeLoss() ? trackStatistics.getTotalAltitudeLoss() : loss_m;
avgHeartRate = trackStatistics.getAverageHeartRate();
avgPower = trackStatistics.getAveragePower();
if (lastTrackPoint == null) {
return;
}
@@ -183,6 +196,7 @@ public class IntervalStatistics {
gain_m = trackStatistics.hasTotalAltitudeGain() ? trackStatistics.getTotalAltitudeGain() : gain_m;
loss_m = trackStatistics.hasTotalAltitudeLoss() ? trackStatistics.getTotalAltitudeLoss() : loss_m;
avgHeartRate = trackStatistics.getAverageHeartRate();
avgPower = trackStatistics.getAveragePower();
}
}
}
+4
View File
@@ -728,4 +728,8 @@ limitations under the License.
<string name="introduction_osm_dashboard">OpenTracks itself does not provide a map. Please install OSMDashboard to view your recordings on a map.</string>
<string name="settings_recording_min_sampling_interval_title">Sampling time interval</string>
<string name="settings_recording_location_distance_summary">%1$s between recorded locations</string>
<string name="voice_announce_lap_power_key">voiceAnnounceLapPower</string>
<string name="settings_announcements_lap_power">Lap power</string>
<string name="power_x_watt">Power {x, plural, =1 {1 watt} other {# watts} }</string>
<string name="power">Power</string>
</resources>
+21 -18
View File
@@ -25,6 +25,13 @@
</PreferenceCategory>
<PreferenceCategory android:title="@string/settings_announcements_statistics_title">
<SwitchPreferenceCompat
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:defaultValue="@bool/voice_announce_unit_default"
android:key="@string/voice_announce_unit_key"
android:title="@string/settings_announcements_unit" />
<ListPreference
android:defaultValue="@string/voice_announcement_frequency_default"
android:entryValues="@array/voice_announcement_frequency_values"
@@ -32,13 +39,6 @@
android:title="@string/settings_announcements_totaltime_frequency"
app:useSimpleSummaryProvider="true" />
<ListPreference
android:defaultValue="@string/voice_announcement_distance_default"
android:entryValues="@array/voice_announcement_distance_values"
android:key="@string/voice_announcement_distance_key"
android:title="@string/settings_announcements_distance_frequency"
app:useSimpleSummaryProvider="true" />
<SwitchPreferenceCompat
android:defaultValue="@bool/voice_announce_total_distance_default"
android:key="@string/voice_announce_total_distance_key"
@@ -54,26 +54,29 @@
android:key="@string/voice_announce_average_speed_pace_key"
android:title="@string/settings_announcements_average_speed_pace" />
<SwitchPreferenceCompat
android:defaultValue="@bool/voice_announce_lap_speed_pace_default"
android:key="@string/voice_announce_lap_speed_pace_key"
android:title="@string/settings_announcements_lap_speed_pace" />
<SwitchPreferenceCompat
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:defaultValue="@bool/voice_announce_unit_default"
android:key="@string/voice_announce_unit_key"
android:title="@string/settings_announcements_unit" />
<SwitchPreferenceCompat
android:defaultValue="@bool/voice_announce_average_heart_rate_default"
android:key="@string/voice_announce_average_heart_rate_key"
android:title="@string/settings_announcements_average_heart_rate" />
<ListPreference
android:defaultValue="@string/voice_announcement_distance_default"
android:entryValues="@array/voice_announcement_distance_values"
android:key="@string/voice_announcement_distance_key"
android:title="@string/settings_announcements_distance_frequency"
app:useSimpleSummaryProvider="true" />
<SwitchPreferenceCompat
android:defaultValue="@bool/voice_announce_lap_speed_pace_default"
android:key="@string/voice_announce_lap_speed_pace_key"
android:title="@string/settings_announcements_lap_speed_pace" />
<SwitchPreferenceCompat
android:defaultValue="@bool/voice_announce_lap_heart_rate_default"
android:key="@string/voice_announce_lap_heart_rate_key"
android:title="@string/settings_announcements_lap_heart_rate" />
<SwitchPreferenceCompat
android:defaultValue="false"
android:key="@string/voice_announce_lap_power_key"
android:title="@string/settings_announcements_lap_power" />
</PreferenceCategory>
</PreferenceScreen>