diff --git a/src/main/java/de/dennisguse/opentracks/services/announcement/VoiceAnnouncementUtils.java b/src/main/java/de/dennisguse/opentracks/services/announcement/VoiceAnnouncementUtils.java
index 1dfeedf36..f902b8618 100644
--- a/src/main/java/de/dennisguse/opentracks/services/announcement/VoiceAnnouncementUtils.java
+++ b/src/main/java/de/dennisguse/opentracks/services/announcement/VoiceAnnouncementUtils.java
@@ -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;
}
diff --git a/src/main/java/de/dennisguse/opentracks/settings/PreferencesUtils.java b/src/main/java/de/dennisguse/opentracks/settings/PreferencesUtils.java
index d12760e1c..5f2077091 100644
--- a/src/main/java/de/dennisguse/opentracks/settings/PreferencesUtils.java
+++ b/src/main/java/de/dennisguse/opentracks/settings/PreferencesUtils.java
@@ -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);
diff --git a/src/main/java/de/dennisguse/opentracks/stats/TrackStatistics.java b/src/main/java/de/dennisguse/opentracks/stats/TrackStatistics.java
index 41fa6d8e7..976f9b618 100644
--- a/src/main/java/de/dennisguse/opentracks/stats/TrackStatistics.java
+++ b/src/main/java/de/dennisguse/opentracks/stats/TrackStatistics.java
@@ -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;
}
diff --git a/src/main/java/de/dennisguse/opentracks/stats/TrackStatisticsUpdater.java b/src/main/java/de/dennisguse/opentracks/stats/TrackStatisticsUpdater.java
index 31dc37936..36fb7fae0 100644
--- a/src/main/java/de/dennisguse/opentracks/stats/TrackStatisticsUpdater.java
+++ b/src/main/java/de/dennisguse/opentracks/stats/TrackStatisticsUpdater.java
@@ -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;
diff --git a/src/main/java/de/dennisguse/opentracks/ui/intervals/IntervalStatistics.java b/src/main/java/de/dennisguse/opentracks/ui/intervals/IntervalStatistics.java
index cbb07bd06..8ce364f71 100644
--- a/src/main/java/de/dennisguse/opentracks/ui/intervals/IntervalStatistics.java
+++ b/src/main/java/de/dennisguse/opentracks/ui/intervals/IntervalStatistics.java
@@ -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();
}
}
}
diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml
index ea8047449..4fb7e15c0 100644
--- a/src/main/res/values/strings.xml
+++ b/src/main/res/values/strings.xml
@@ -728,4 +728,8 @@ limitations under the License.
OpenTracks itself does not provide a map. Please install OSMDashboard to view your recordings on a map.
Sampling time interval
%1$s between recorded locations
+ voiceAnnounceLapPower
+ Lap power
+ Power {x, plural, =1 {1 watt} other {# watts} }
+ Power
diff --git a/src/main/res/xml/settings_announcements.xml b/src/main/res/xml/settings_announcements.xml
index bb12c55c3..b4eca9a80 100644
--- a/src/main/res/xml/settings_announcements.xml
+++ b/src/main/res/xml/settings_announcements.xml
@@ -25,6 +25,13 @@
+
+
-
-
-
-
-
+
+
+